This is ouedkniss.com the top Algerian e-commerce website where usually real estate agencies and property owners list their properties

We will start our journey by scraping the properties listing for Algiers for one year history



To do this we are going to use rvest library for webscraping and puuur package for functional programming

For all the rest we are going to use the tidyverse packages

The tidyverse is an opinionated collection of R packages designed for data science.

All packages share an underlying design philosophy, grammar, and data structures



The information that we are looking for is hiden into the website’s html, we are going to use Chrome

SelectorGadget extension to catch the CssSelector of the elements that we want to extract.



let’s first have a look on how rvest and purrr work



Purrrr cheat sheet from Rstudio





Let’s start by loading the needed packages

library(tidyverse)              #For Almost everything
library(rvest)                  #For Web Scraping
library(DT)                     #For formating tables plotting in html
library(lubridate)              #For Date and Times manipulation
library(tictoc)                 #For calculating time execution 
library(maptools)               #For reading shape files
library(ggmap)                  #Get the geolocation

Starting the Data Scraping

scraping one page

Sys.setlocale("LC_CTYPE","Arabic_Saudi Arabia") #change the locale system to get proper arabic 
[1] "Arabic_Saudi Arabia.1256"
# Store web url
immo_url <- ("https://www.ouedkniss.com/annonces/index.php?c=immobilier&sc=vente&sc2=appartement&wilaya=%2Calger&prix=1&prix_unite=2&p=1")
immo_url
[1] "https://www.ouedkniss.com/annonces/index.php?c=immobilier&sc=vente&sc2=appartement&wilaya=%2Calger&prix=1&prix_unite=2&p=1"
Links <- immo_url %>% read_html(.,encoding = "UTF-8") %>%          #Read the html of the url
                               html_nodes(".button_details") %>%   #Select button_details element
                               html_attr("href")                   #Extract all hyperlinks text       
Links # print the content of Links
 [1] "vente-appartement-f3-alger-bab-ezzouar-algerie-immobilier-d16678458"     "vente-appartement-f3-alger-birtouta-algerie-immobilier-d16680050"       
 [3] "vente-appartement-f2-alger-birtouta-algerie-immobilier-d14890818"        "vente-appartement-f4-alger-birtouta-algerie-immobilier-d14988773"       
 [5] "vente-appartement-f4-alger-centre-algerie-immobilier-d16676423"          "vente-appartement-f3-alger-baba-hassen-algerie-immobilier-d16678782"    
 [7] "vente-appartement-f2-alger-bab-el-oued-algerie-immobilier-d16675650"     "vente-appartement-f4-alger-bouzareah-algerie-immobilier-d16678633"      
 [9] "vente-appartement-f2-alger-centre-algerie-immobilier-d16675612"          "vente-appartement-f5-alger-kouba-algerie-immobilier-d14025834"          
[11] "vente-appartement-f3-alger-birkhadem-algerie-immobilier-d16270056"       "vente-appartement-f2-alger-oued-smar-algerie-immobilier-d16678170"      
[13] "vente-appartement-f3-alger-draria-algerie-immobilier-d15990036"          "vente-appartement-f3-alger-bordj-el-kiffan-algerie-immobilier-d16677974"
[15] "vente-appartement-f3-alger-bordj-el-kiffan-algerie-immobilier-d16193073" "vente-appartement-f4-alger-el-mouradia-algerie-immobilier-d16391032"    
[17] "vente-appartement-f4-alger-zeralda-algerie-immobilier-d13299882"         "vente-appartement-f3-alger-ain-naadja-algerie-immobilier-d16677515"     
[19] "vente-appartement-f5-alger-el-mouradia-algerie-immobilier-d15734403"     "vente-appartement-f3-alger-baraki-algerie-immobilier-d16676668"         
[21] "vente-appartement-f3-alger-zeralda-algerie-immobilier-d15967704"         "vente-appartement-f3-alger-bab-ezzouar-algerie-immobilier-d16675834"    
[23] "vente-appartement-f3-alger-el-harrach-algerie-immobilier-d13034379"      "vente-appartement-f2-alger-baraki-algerie-immobilier-d16675458"         
[25] "vente-appartement-f2-alger-said-hamdine-algerie-immobilier-d16675267"    "vente-appartement-f5-alger-bab-ezzouar-algerie-immobilier-d16675171"    
[27] "vente-appartement-f3-alger-ouled-fayet-algerie-immobilier-d15721644"     "vente-appartement-f2-alger-el-biar-algerie-immobilier-d11289501"        
[29] "vente-appartement-f2-alger-baraki-algerie-immobilier-d16540460"          "vente-appartement-f4-alger-baraki-algerie-immobilier-d11916108"         
Links <- Links %>%
        unlist() %>%                                               #transform Links to a vector
        paste("https://www.ouedkniss.com/", ., sep = "")           #concatenate the website root address with the links
                                                                                                        
Links
 [1] "https://www.ouedkniss.com/vente-appartement-f3-alger-bab-ezzouar-algerie-immobilier-d16678458"    
 [2] "https://www.ouedkniss.com/vente-appartement-f3-alger-birtouta-algerie-immobilier-d16680050"       
 [3] "https://www.ouedkniss.com/vente-appartement-f2-alger-birtouta-algerie-immobilier-d14890818"       
 [4] "https://www.ouedkniss.com/vente-appartement-f4-alger-birtouta-algerie-immobilier-d14988773"       
 [5] "https://www.ouedkniss.com/vente-appartement-f4-alger-centre-algerie-immobilier-d16676423"         
 [6] "https://www.ouedkniss.com/vente-appartement-f3-alger-baba-hassen-algerie-immobilier-d16678782"    
 [7] "https://www.ouedkniss.com/vente-appartement-f2-alger-bab-el-oued-algerie-immobilier-d16675650"    
 [8] "https://www.ouedkniss.com/vente-appartement-f4-alger-bouzareah-algerie-immobilier-d16678633"      
 [9] "https://www.ouedkniss.com/vente-appartement-f2-alger-centre-algerie-immobilier-d16675612"         
[10] "https://www.ouedkniss.com/vente-appartement-f5-alger-kouba-algerie-immobilier-d14025834"          
[11] "https://www.ouedkniss.com/vente-appartement-f3-alger-birkhadem-algerie-immobilier-d16270056"      
[12] "https://www.ouedkniss.com/vente-appartement-f2-alger-oued-smar-algerie-immobilier-d16678170"      
[13] "https://www.ouedkniss.com/vente-appartement-f3-alger-draria-algerie-immobilier-d15990036"         
[14] "https://www.ouedkniss.com/vente-appartement-f3-alger-bordj-el-kiffan-algerie-immobilier-d16677974"
[15] "https://www.ouedkniss.com/vente-appartement-f3-alger-bordj-el-kiffan-algerie-immobilier-d16193073"
[16] "https://www.ouedkniss.com/vente-appartement-f4-alger-el-mouradia-algerie-immobilier-d16391032"    
[17] "https://www.ouedkniss.com/vente-appartement-f4-alger-zeralda-algerie-immobilier-d13299882"        
[18] "https://www.ouedkniss.com/vente-appartement-f3-alger-ain-naadja-algerie-immobilier-d16677515"     
[19] "https://www.ouedkniss.com/vente-appartement-f5-alger-el-mouradia-algerie-immobilier-d15734403"    
[20] "https://www.ouedkniss.com/vente-appartement-f3-alger-baraki-algerie-immobilier-d16676668"         
[21] "https://www.ouedkniss.com/vente-appartement-f3-alger-zeralda-algerie-immobilier-d15967704"        
[22] "https://www.ouedkniss.com/vente-appartement-f3-alger-bab-ezzouar-algerie-immobilier-d16675834"    
[23] "https://www.ouedkniss.com/vente-appartement-f3-alger-el-harrach-algerie-immobilier-d13034379"     
[24] "https://www.ouedkniss.com/vente-appartement-f2-alger-baraki-algerie-immobilier-d16675458"         
[25] "https://www.ouedkniss.com/vente-appartement-f2-alger-said-hamdine-algerie-immobilier-d16675267"   
[26] "https://www.ouedkniss.com/vente-appartement-f5-alger-bab-ezzouar-algerie-immobilier-d16675171"    
[27] "https://www.ouedkniss.com/vente-appartement-f3-alger-ouled-fayet-algerie-immobilier-d15721644"    
[28] "https://www.ouedkniss.com/vente-appartement-f2-alger-el-biar-algerie-immobilier-d11289501"        
[29] "https://www.ouedkniss.com/vente-appartement-f2-alger-baraki-algerie-immobilier-d16540460"         
[30] "https://www.ouedkniss.com/vente-appartement-f4-alger-baraki-algerie-immobilier-d11916108"         
# we will show  how it works for one link
Page <-  read_html(Links[1],encoding = "UTF-8") 
Page #print the content of Page
{xml_document}
<html lang="fr">
[1] <head>\n<base href="//www.ouedkniss.com/">\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n<meta name="google-site-verification" content="z ...
[2] <body>\n\t<script>user_agent='desktop';</script><script>var user_agent_forced=false;</script><script>language='fr';</script><div id="HTML" class="html">\n\t\t\n\ ...
#Let's extract the Title
Title <- Page %>%                                        #Is our Html document
         html_nodes("#Title") %>%                        #We are selecting the Title element 
         html_text()                                     #Extracting the text from the Title element in the Html document
        
Title
[1] "Vente Appartement F3 Alger Bab ezzouar"
#Getting the Description
Description <- Page %>% 
        html_nodes("#GetDescription") %>% 
        html_text()
            
Description
[1] "Cité Rabia Tahar, Bab-Ezzouar, vend un Appartement de type F-3 de 75 m2, 5ème étage, Acte. Prix : 1,8 Milliard négociable."
#Geting the Price with the details
Details <- Page %>% 
        html_nodes("#Prix,#Description p") %>% 
        html_text()
Details
[1] "Numéro : 16678458"                 "Nombre de vues : 33"               "Déposée le : 03-08-2018 à 18:30"   " Quartier :  Cité Rabia Tahar"    
[5] " Nombre de pièces :  3"            " Nombre d'étages / étage :  5"     " Superficie :  75 M²"              " Spécifications :   Acte notarié "
[9] "Prix :  18 Millions Négociable"   
#Getting the store name
Store.Name <-  html_nodes(Page, "#store_name") %>%      #Yes, it can be written also like this
        html_text()                                        
       
Store.Name
[1] "Day Immobilier"
#Getting the store address
Store.Addresse<- Page %>% 
        html_nodes("#store_adresse") %>% 
        html_text()
Store.Addresse
[1] "Alger Bab ezzouar cité 5 juillet Bt 110 Bab Ezzouar"
#Getting the pseudo for the owners
Pseudo <- Page %>% 
        html_nodes(".Pseudo") %>% 
        html_text()
Pseudo <- NA
#Now we need to group all the elements extracted in one table  
#We have different ttype of elements with different length
map_dbl(list(Links[1],Title, Description,Details,Store.Name, Pseudo ), length)
[1] 1 1 1 9 1 1
#we can use a list to group all elements but better is to use a tibble, a new enhanced data frame structure  
#that accepts the columns to be list
one_link_data <- tibble(Links[1],Title, Description,Details,Store.Name, Pseudo) 
one_link_data
# replace empty details with NA
one_link_data <-   one_link_data %>%
               mutate(Title = as.character(ifelse(Title=="character(0)", NA, Title)),
               Description = as.character(ifelse(Description=="character(0)", NA, Description)),
               Details = ifelse(Details=="character(0)", NA, Details),
               Store.Name = as.character(ifelse(Store.Name=="character(0)", NA, Store.Name)),
               Store.Addresse = as.character(ifelse(Store.Addresse=="character(0)", NA, Store.Addresse)),
               Pseudo = as.character(ifelse(Pseudo=="character(0)", NA, Pseudo)),
               # Extract details elements and values from post Description
               Det = map(Details, function(x) str_trim(str_split_fixed(string = x[], pattern = " : ", n=2)[,1])),
               Values = map(Details, function(x) str_trim(str_split_fixed(string = x[], pattern = " : ", n=2)[,2]))
               # Det and Values are list columns , we have nested data frame now
               ) %>%
        select(-Details)
one_link_data
# Get rid of nested columns by unnesting the data
one_link_data <- one_link_data %>%  unnest() 
one_link_data
# Do some cleansing
one_link_data <- one_link_data %>%
        filter(Det != "")%>%                                                            # remove white space
        mutate(Det =  case_when(                                                        # replace with correct spelling
                .$Det == "Nombre de pièces" ~ "Nombre de pièces",
                .$Det == "Spécifications" ~ "Spécifications",
                .$Det == "Nombre d'étages / étage" ~ "Nombre d'étages / étage",
                .$Det == "Déposée le" ~ "Déposée le",
                .$Det == "Numéro" ~ "Numéro",
                TRUE ~ as.character(.$Det)
                )) %>% 
        # the next three lines to remove some duplicates
        group_by(Links[1], Det) %>%
        arrange(desc(Values)) %>%
        slice(1) 
one_link_data
#choose the record with the highest Values and drop the other duplicates
#tidying the data, now we will have 1 row per housing listing and all other details on columns
one_link_data <- one_link_data %>% spread(Det,value = Values) 
one_link_data
NA

Creating the scraper function

scrape_houses <- function(nb.page = 1){

Sys.setlocale("LC_CTYPE","Arabic_Saudi Arabia") #change the locale system to get proper arabic text

# Store web url
immo_url <- ("https://www.ouedkniss.com/annonces/index.php?c=immobilier&sc=vente&sc2=appartement&wilaya=%2Calger&prix=1&prix_unite=2&p=") %>%
             paste (. , nb.page, sep = "")

# Scrape the post links from the page urls

#Getting all the links of the root page from the details button
#We are going to use possibly which replace any errors in the scraping with a lign of NA's, in order to avoid  
#the early stopping of the scraping due to the errors
tic("links") # Encapsuling each step bewteen tic toc commandes to calculate the execution time

Links <- map(immo_url, possibly(.%>%read_html(.,encoding = "UTF-8") %>% 
                               html_nodes(".button_details") %>% 
                               html_attr("href")
             ,NA_real_)) %>%
        unlist() %>% 
        paste("https://www.ouedkniss.com/", ., sep = "")
toc()


tic("pages")
#Getting all the pages of the links 
#In order to do it for all tha links we need to use map function 
Pages <- map(Links,                                      #First argument of the map function is our data
             possibly(                                   # We are using possibly to change the behavior of any error message
                     read_html(.,encoding = "UTF-8")  ,  #the second argument of the map function is the function that we want to apply
                      NA_real_))                         #We are going to fill our data with NA's if any error ocuur

toc()

# Getting  the post title
tic("titles")
Title <- map(Pages,possibly(. %>% 
                         html_nodes("#Title") %>% 
                         html_text()
                 ,NA_real_))
toc()

#Getting the Description
tic("description")
Description <- map(Pages,possibly(. %>% 
                         html_nodes("#GetDescription") %>% 
                         html_text()
                 ,NA_real_))
toc()

#Geting the Price with the details
tic("details")
Details <- map(Pages,possibly(. %>% 
                         html_nodes("#Prix,#Description p") %>% 
                         html_text()
                 ,NA_real_))
toc()

#Getting the store name
tic("store.name")
Store.Name<- map(Pages,possibly(. %>% 
                         html_nodes("#store_name") %>% 
                         html_text()
                 ,NA_real_)) 
toc()

#Getting the store address
tic("store.addresse")
Store.Addresse<- map(Pages,possibly(. %>% 
                         html_nodes("#store_adresse") %>% 
                         html_text()
                  ,NA_real_))
toc()

#Getting the pseudo for the owners
tic("pseudo")
Pseudo <- map(Pages,possibly(. %>% 
                         html_nodes(".Pseudo") %>% 
                         html_text()
              ,NA_real_)) 
toc() 

# adding everything to a tibble and reshaping the data 
tic("constructing tibble")        
housing_data <- tibble(Links,Title, Description,Details,Store.Name, Pseudo) %>%  
        # replace empty details with NA
        mutate(Title = as.character(ifelse(Title=="character(0)", NA, Title)),
               Description = as.character(ifelse(Description=="character(0)", NA, Description)),
               Details = ifelse(Details=="character(0)", NA, Details),
               Store.Name = as.character(ifelse(Store.Name=="character(0)", NA, Store.Name)),
               Store.Addresse = as.character(ifelse(Store.Addresse=="character(0)", NA, Store.Addresse)),
               Pseudo = as.character(ifelse(Pseudo=="character(0)", NA, Pseudo)),
               # Extract details elements and values from post Description
               Det = map(Details, function(x) str_trim(str_split_fixed(string = x[], pattern = " : ", n=2)[,1])),
               Values = map(Details, function(x) str_trim(str_split_fixed(string = x[], pattern = " : ", n=2)[,2]))
               # Det and Values are list columns , we have nested data frame now
               ) %>%
        select(-Details) %>%
        # Get rid of nested columns by unnesting the data
        unnest() %>%
        # Do some cleansing
        filter(Det != "")%>%
        mutate(Det =  case_when(
                .$Det == "Nombre de pièces" ~ "Nombre de pièces",
                .$Det == "Spécifications" ~ "Spécifications",
                .$Det == "Nombre d'étages / étage" ~ "Nombre d'étages / étage",
                .$Det == "Déposée le" ~ "Déposée le",
                .$Det == "Numéro" ~ "Numéro",
                TRUE ~ as.character(.$Det)
                )) %>% 
        # the next three lines to remove some duplicates
        group_by(Links, Det) %>%
        arrange(desc(Values)) %>%
        slice(1) %>% # choose the record with the highest Values and drop the other duplicates
        # tidying the data, now we will have 1 row per housing listing and all other details on columns
        spread(Det,value = Values) 
 toc()
 tic("return")
return(housing_data)
 toc()
}

Scrape the housing data and save it to the disk

#Create a gentle scraping function
Asber_Chouia <- function (periods = c(1,1.5)) {
        SleepCalls <-  runif(1, periods[1],periods[2]) # generate a uniform random value  between period 1 and period 2
        # some prinitng to seperate the execution
        cat(paste("----------------------------------------------------------------------------------------","",sep = "\n")) 
        cat(paste0(Sys.time()),"Rani Saber", round(SleepCalls,2), "Seconds\n")
        cat(paste("","", sep = "\n"))
        Sys.sleep(SleepCalls) #Cause the sytem to sleep before continue the script execution
}

#Wrap everything into a function
GentleScraping <- function(Start_Sleep=1, Finish_Sleep=1.5, Page){
        Asber_Chouia (c(Start_Sleep, Finish_Sleep))
        Algiers_SalesAppartments <- scrape_houses(Page)
        return(Algiers_SalesAppartments)
}


starttime<- Sys.time() # To count execution time
#Apply the scraping function for 100 pages (that correspond to one year listing)
data_scraped <-  vector("list",100)  #First crate an empty list of 100 element
for (i in 1:100)
{
        cat(paste("","", sep = "\n"))
        cat(paste0("Scraping Page"," ",i))
        cat(paste("","", sep = "\n"))
        # iterate through  all the pages, in each page there is 30 links to listing houses  
        # for each 30 links scraped the scraping will pause for a period of few seconds  
        # before continuing the scraping
        data_scraped[[i]]<- GentleScraping (Start_Sleep =1 ,Finish_Sleep =1.5 ,Page = i)  # iterate through    
}
endtime<- Sys.time()
endtime - starttime # Give the scraping time

Algiers_SaleAppartment <- bind_rows(data_scraped) # append all the element of the list into one big data frame
#Save the data to the disk
write.csv(Algiers_SaleAppartment, "Algiers_SalesAppartments26072018.csv", row.names = FALSE, fileEncoding = "UTF-8") 

Data Preparation and cleansing

housing <- read.csv("Algiers_SalesAppartments26072018.csv", fileEncoding = "UTF-8") ### reading the data 
names(housing) [7:15]<- c("Date", "Nb.Floor", "Nb.Room", "Nb.Views", "ID.Offer", "Price", "District", "Specifics", "Area") ### rename some variables
#create some new variables based on the information found on the existing ones
housing  <- housing %>%
        mutate(Price.value = as.numeric(str_split_fixed(str_trim(housing$Price), " ",3)[,1]),
               Price.unit= factor(str_split_fixed(str_trim(housing$Price), " ",3)[,2]),
               Price.desc= factor(str_split_fixed(str_trim(housing$Price), " ",3)[,3]),
               Area = as.numeric(str_split_fixed(str_trim(housing$Area), " ",2)[,1])
        ) %>%
        mutate(Price.value.dzd = # Create some rules to clean the price
                       case_when(.$Price.unit == "Milliards"  & .$Price.value <=10
                                 ~ .$Price.value *10000000,
                                 str_detect(.$Price.desc, "m²") == TRUE & .$Price.value <= 35 
                                 ~ .$Price.value * .$Area*10000,
                                 str_detect(.$Price.desc, "m²") == TRUE & .$Price.value > 35 & .$Price.value <10000 
                                 ~ .$Price.value *10000,
                                 str_detect(.$Price.desc, "m²") == TRUE & .$Price.value >10000 
                                 ~ .$Price.value * .$Area,
                                 .$Price.unit == "Millions"& .$Price.value <=10 
                                 ~ .$Price.value * 10000000,
                                 .$Price.unit == "Millions"& .$Price.value >100 
                                 ~ .$Price.value * 10000,
                                 .$Price.unit == "Millions"& .$Price.value >10 & .$Price.value <100 
                                 ~.$Price.value * 1000000,
                                 TRUE ~ .$Price.value
                                  ),
         # Create a variable that containg nominal rules applied to Price variable, so that we can use it in our sanity check
               Price.rules = 
                       case_when(.$Price.unit == "Milliards"  & .$Price.value <=10
                                 ~ "Price.value *10000000",
                                 str_detect(.$Price.desc, "m²") == TRUE & .$Price.value <= 35 
                                 ~ "Price.value * Area*10000",
                                 str_detect(.$Price.desc, "m²") == TRUE & .$Price.value > 35 & .$Price.value <10000 
                                 ~ "Price.value *10000",
                                 str_detect(.$Price.desc, "m²") == TRUE & .$Price.value >10000 
                                 ~ "Price.value * Area",
                                 .$Price.unit == "Millions"& .$Price.value <=10 
                                 ~ "Price.value * 10000000",
                                 .$Price.unit == "Millions"& .$Price.value >100 
                                 ~ "Price.value * 10000",
                                 .$Price.unit == "Millions"& .$Price.value >10 & .$Price.value <100 
                                 ~"Price.value * 1000000",
                                 TRUE ~ "Price.value"
                                  ),
              # Create even more variables
              # Announcer.Name = ifelse(is.na(Pseudo) == FALSE,Pseudo ,as.character(Store.Name),
              # Announcer.Type = case_when(is.na(Store.Addresse) == FALSE & str_detect(toupper(Store.Name), "AGENCE|AG") == TRUE ~ "AGENCE",
              #                            is.na(Store.Addresse) == FALSE & str_detect(toupper(Store.Name), "PROMOTION") == TRUE ~ "PROMOTEUR " ,
              #                            TRUE ~ "BUREAU D'AFFAIRE"),
              Hour =  str_sub(str_trim(str_split_fixed(Date,"à", 2)[,2]),1,2),
              Date = dmy(str_trim(str_split_fixed(Date,"à", 2)[,1])),
              Month = month(Date,label = TRUE),
              Municipality=factor(str_trim(str_extract(Title,"(Alger.*)"))),
              Garage=str_detect(Specifics, "Garage"),
              Garden=str_detect(Specifics, "Jardin") ,
              Furnished=str_detect(Specifics, "Meublé"),
              Promise=str_detect(Specifics, "Promesse de vente"),
              New.Project=str_detect(Specifics, "Promotion"),
              Payment=factor(ifelse(str_detect(Specifics,"Paiement par tranches") == TRUE, "tranches", "comptant"))
              )
               
housing
write.csv(housing, "Algiers_SalesAppartments26072018_c.csv", row.names = FALSE, fileEncoding = "UTF-8")



Having a look at our data after cleansing

housing %>% select(Date,Nb.Floor,Nb.Room,Nb.Views) %>% summary() 
      Date               Nb.Floor            Nb.Room         Nb.Views      
 Min.   :2017-07-18   Min.   :        1   Min.   :1.000   Min.   :    9.0  
 1st Qu.:2018-04-11   1st Qu.:        1   1st Qu.:3.000   1st Qu.:  104.0  
 Median :2018-06-24   Median :        3   Median :3.000   Median :  241.0  
 Mean   :2018-05-15   Mean   :   240520   Mean   :3.269   Mean   :  911.8  
 3rd Qu.:2018-07-19   3rd Qu.:        4   3rd Qu.:4.000   3rd Qu.:  736.0  
 Max.   :2018-07-26   Max.   :552625820   Max.   :8.000   Max.   :57688.0  
                      NA's   :402         NA's   :93      NA's   :144      
housing %>% select(Area,Price.value.dzd,Municipality) %>% summary() 
      Area           Price.value.dzd                    Municipality 
 Min.   :1.000e+00   Min.   :        1   Alger Alger centre   : 223  
 1st Qu.:7.000e+01   1st Qu.:  8500000   Alger Bab ezzouar    : 153  
 Median :8.000e+01   Median : 14000000   Alger Cheraga        : 131  
 Mean   :1.115e+06   Mean   : 16836916   Alger Bordj el bahri : 111  
 3rd Qu.:1.050e+02   3rd Qu.: 22000000   Alger Bordj el kiffan: 110  
 Max.   :2.147e+09   Max.   :100000000   Alger El achour      : 107  
 NA's   :179                             (Other)              :1865  
housing %>% select(Garage:Payment) %>% summary() 
   Garage          Garden        Furnished        Promise        New.Project         Payment    
 Mode :logical   Mode :logical   Mode :logical   Mode :logical   Mode :logical   comptant:2310  
 FALSE:2031      FALSE:2197      FALSE:2273      FALSE:2257      FALSE:2098      tranches: 152  
 TRUE :431       TRUE :265       TRUE :189       TRUE :205       TRUE :364       NA's    : 238  
 NA's :238       NA's :238       NA's :238       NA's :238       NA's :238                      



let’s apply some more filters and prepare the data for geocoding

## remove abnormal data 
housing2 <- housing %>%
        filter(Price.value.dzd <100000000 & Price.value.dzd >= 4000000,Area >30 & Area <= 300,    #Applying some filters 
               str_trim(Nb.Room) %in% c("2","3","4","5")) %>%
        mutate(Nb.Room = factor(Nb.Room)) 
housing2$Nb.Room <- ordered(str_trim(housing2$Nb.Room) ,levels =1:7) #Ordering the levels of the Nb.Room variable  

# construct a function for multiple pattern replacement
mgsub <- function(pattern, replacement, x, ...) {
  if (length(pattern)!=length(replacement)) {
    stop("pattern and replacement do not have the same length.")
  }
  result <- x
  for (i in 1:length(pattern)) {
    result <- gsub(pattern[i], replacement[i], result, ...)
  }
  result
} 

#read the shape file for Algiers
sh<-readShapePoly("C:/Users/fateh/Documents/R Scripts/Shapefile/algeria/communes.shp")
sh<-sh[sh@data$wilaya=="ALGER",]


#Do some cleansing and preparation in order to match the names of the municipalities in ouedkniss to those 
#in our shape file
housing2$Municipality<-  toupper(str_split_fixed(string =housing2$Municipality, pattern = " ",n = 2 )[,2])
housing2$Municipality <- factor(ifelse(str_detect (string = housing2$Links,pattern = "alger-centre") == TRUE & is.na(housing2$District) == TRUE,
                                "ALGER CENTRE", as.character(housing2$Municipality))) 
#Correct the naming
housing2$Municipality<- mgsub(c("GUE DE CONSTANTINE", "BAB EZZOUAR","BACHDJERRAH","HAMMAMET", "BIRKHADEM",
                                    "BELOUIZDAD","BOLOGHINE","ALGER CENTRE", "CHEVALLEY","HRAOUA","TESSALA EL MERDJA","SAID HAMDINE","AIN NAADJA"),
      c("DJISR KSENTINA","BEB EZZOUAR","BACH DJERRAH", "BAINS ROMAINS", "BIR KHADEM", "HAMMA ANASSERS",
        "BOLOGHINE IBN ZIRI", "ALGER" ,"BOUZAREAH", "HARAOUA", "TASSALA EL MERDJA","BIR MOURAD RAIS","DJISR KSENTINA"),
     housing2$Municipality)

housing2$Municipality <- factor(ifelse(housing2$Municipality== "","ALGER", as.character(housing2$Municipality)))
##Creating top municipalities variable
housing2<- housing2%>% mutate(Top_Municipalities = fct_lump(housing2$Municipality, 10)) 
housing2 %>% select(Date,Nb.Floor,Nb.Room,Nb.Views) %>% summary() 
      Date               Nb.Floor        Nb.Room     Nb.Views      
 Min.   :2017-07-18   Min.   :    1.00   1:   0   Min.   :    9.0  
 1st Qu.:2018-04-15   1st Qu.:    1.00   2: 350   1st Qu.:  104.0  
 Median :2018-06-24   Median :    3.00   3:1051   Median :  242.0  
 Mean   :2018-05-16   Mean   :   46.21   4: 611   Mean   :  931.9  
 3rd Qu.:2018-07-19   3rd Qu.:    4.00   5: 140   3rd Qu.:  724.0  
 Max.   :2018-07-26   Max.   :14567.00   6:   0   Max.   :57688.0  
                      NA's   :292        7:   0   NA's   :112      
housing2 %>% select(Area,Price.value.dzd,Municipality) %>% summary() 
      Area           Price.value.dzd             Municipality 
 Min.   :3.100e+01   Min.   : 4000000   ALGER          : 176  
 1st Qu.:7.000e+01   1st Qu.:10000000   BEB EZZOUAR    : 112  
 Median :8.000e+01   Median :15500000   CHERAGA        : 111  
 Mean   :1.306e+06   Mean   :18606147   DJISR KSENTINA : 100  
 3rd Qu.:1.050e+02   3rd Qu.:23500000   BORDJ EL BAHRI :  96  
 Max.   :2.147e+09   Max.   :95000000   BORDJ EL KIFFAN:  95  
                                        (Other)        :1462  
housing2 %>% select(Garage:Payment) %>% summary() 
   Garage          Garden        Furnished        Promise        New.Project         Payment    
 Mode :logical   Mode :logical   Mode :logical   Mode :logical   Mode :logical   comptant:1853  
 FALSE:1633      FALSE:1769      FALSE:1841      FALSE:1819      FALSE:1680      tranches: 127  
 TRUE :347       TRUE :211       TRUE :139       TRUE :161       TRUE :300       NA's    : 172  
 NA's :172       NA's :172       NA's :172       NA's :172       NA's :172                      
data.frame(Top_Municipalities =table(housing2$Top_Municipalities))## check

unique(sort(factor(housing2$Municipality)))
unique(sort(factor(sh@data$commune0)))## check again nehi twesswiss



Get The geolocation of the Housing Munucipalities from Google Map API

#Get the geolocation of the 
commune.names <- paste("Algiers,", as.character(unique(housing2$Municipality)), sep=" ")
commune.names[commune.names == "Algiers, ALGER"] <- "ALGER CENTRE, Algiers"
commune.info <- geocode(commune.names, output = "more", override_limit = TRUE)

#Get the localities that was not geocoded in the dirst run
commune.names.missing <- cbind(commune.names,commune.info) %>% 
        filter(is.na(lon))  
#Second geocode pass
commune.info.missing <- geocode(as.character(commune.names.missing$commune.names), output = "more", override_limit = TRUE)


#Bind rows first and second geocoding pass
commune.info_final<-
        cbind(commune.names,commune.info) %>%
        filter(!is.na(lon)) %>%
        bind_rows(.,cbind(commune.names=commune.names.missing$commune.names,commune.info.missing)) 

#Do more cleansing
commune.info_final <-
        mutate(commune.info_final,commune.names = factor(ifelse(commune.names == "ALGER CENTRE, Algiers",
                                                                              "Algiers, ALGER", 
                                                                              as.character(commune.names))))
commune.info_final$Municipality <- trimws(str_split_fixed(commune.info_final$commune.names,",",2))[,2]

#Create an augmented data set with geolocation data
Housing_Data <- 
        housing2%>%
        left_join(., commune.info_final) %>%
        #creating Announcer name and announcer Type variable
        mutate(Announcer.Name = ifelse(is.na(Pseudo) == FALSE,as.character(Pseudo) ,as.character(Store.Name)),
               Announcer.Type = case_when(str_detect(toupper(Store.Name), "AGENCE|AG") == TRUE ~ "AGENCE",
                                          str_detect(toupper(Store.Name), "PROMOTION") == TRUE ~ "PROMOTEUR" ,
                                          str_detect(toupper(Store.Name), "BUREAU|AFFAIRE") == TRUE ~ "BUREAU D'AFFAIRE",
                                          is.na(Pseudo) == FALSE ~ "PARTICULIER",
                                          TRUE ~ "BUREAU D'AFFAIRE")
               ) 
commune.info_final
Housing_Data

Get the geolocation of the Store(Promoteur,Agence, Bureau d’etudes)

#Getting the Municipality of the Store by looking for similar address pattern between the House Municipality and the Store Address
Housing_Data$Store.Municipality <- 
        str_match(toupper(housing2$Store.Addresse),
                  pattern = paste(c(unique(as.character(housing2$Municipality))[-10],
                                  c("GUE DE CONSTANTINE", "BAB EZZOUAR","BACHDJERRAH","HAMMAMET", "BIRKHADEM", "BELOUIZDAD","BOLOGHINE",
                                    "ALGER CENTRE", "CHEVALLEY","HRAOUA","TESSALA EL MERDJA","SAID HAMDINE","AIN NAADJA")) ,
                                  collapse = "|")) [,1] 
#Matching the Store Municipality names with the Housing Municipality names
Housing_Data$Store.Municipality<- mgsub(c("GUE DE CONSTANTINE", "BAB EZZOUAR","BACHDJERRAH","HAMMAMET", "BIRKHADEM",
                                    "BELOUIZDAD","BOLOGHINE","ALGER CENTRE", "CHEVALLEY","HRAOUA","TESSALA EL MERDJA","SAID HAMDINE","AIN NAADJA"),
      c("DJISR KSENTINA","BEB EZZOUAR","BACH DJERRAH", "BAINS ROMAINS", "BIR KHADEM", "HAMMA ANASSERS",
        "BOLOGHINE IBN ZIRI", "ALGER" ,"BOUZAREAH", "HARAOUA", "TASSALA EL MERDJA","BIR MOURAD RAIS","DJISR KSENTINA"),
     Housing_Data$Store.Municipality)

#Getting the long and lat for Store Municipality
Housing_Data <-left_join(Housing_Data, select(commune.info_final,Municipality, Store.lon=lon, Store.lat=lat), by = c("Store.Municipality" = "Municipality"))
glimpse(Housing_Data)
Observations: 2,152
Variables: 50
$ Links                       <fctr> https://www.ouedkniss.com/vente-appartement-f2-alger-belouizdad-algerie-immobilier-d16427845, https://www.ouedkniss.com/vente-ap...
$ Title                       <fctr> Vente Appartement F2 Alger Belouizdad, Vente Appartement F2 Alger Birtouta, Vente Appartement F2 Alger Dar el beida, Vente Appar...
$ Description                 <fctr> NA, J'ai une grande,propre et top f2 très bien fini refait à neuf, meublé (cuisine et salle de bain équipée et aménager,wc, gran...
$ Store.Name                  <fctr> NA, NA, NA, NA, MS.Immobilier, NA, APISERVCES, APISERVCES, APISERVCES, APISERVCES, NA, NA, Bureau d'affaire Acs, NA, MT AFFAIRES...
$ Pseudo                      <fctr> youyayouya, F123kniss, megatrain21, kikoum, NA, sidaeli1230, NA, NA, NA, NA, mohamedsebaa, meriemchouch, NA, Ilyes_blh, NA, NA, ...
$ Store.Addresse              <fctr> NA, NA, NA, NA, Alger Bab ezzouar bab ezzouar, NA, Alger El madania 27A, Rue Amar Boudlel, El-Madania- Alger, Alger El madania 2...
$ Date                        <date> 2018-07-26, 2018-07-26, 2018-07-26, 2018-07-26, 2018-07-26, 2018-07-26, 2018-07-26, 2018-07-26, 2018-07-26, 2018-07-26, 2018-07-...
$ Nb.Floor                    <int> NA, 3, 4, 1, 4, 3, 2, 3, 3, NA, 4, 2, 12345, NA, 4, 245, 2, 5, NA, 4, 2, NA, 4, 124, 1234, 12345, 4, NA, NA, 2, 3, 3, 3, 3, 5, 3,...
$ Nb.Room                     <ord> 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, ...
$ Nb.Views                    <int> 119, NA, 33, 381, 9, 39, 15, 17, 19, 19, 36, 11, 210, NA, 24, 5536, 16, NA, 95, 72, 8265, 42, 94, 9966, 3869, 4952, 13, 488, NA, ...
$ ID.Offer                    <int> 16427845, 16340043, 16600827, 16512113, 16603790, 16600079, 16602760, 16602876, 16602975, 16602956, 16599266, 16601484, 16335971,...
$ Price                       <fctr> 860 Millions Négociable, 1 Millions Négociable, 7 Millions Négociable, 900 Millions Offert, 2.4 Milliards Négociable, 750 Millio...
$ District                    <fctr> belcourt, Centre ville, Fatma nsoumer, Cité hayet, 1200 LOGTS, 13 åßÊÇÑ, les sources, la cadat, les vergeres, Résidance rahma, R...
$ Specifics                   <fctr> Electricité ,  Gaz ,  Eau ,  Acte notarié ,  Livret foncier, Electricité ,  Gaz ,  Eau ,  Meublé, Electricité ,  Gaz ,  Eau ,  P...
$ Area                        <dbl> 48, 70, 65, 42, 97, 70, 100, 136, 68, 88, 62, 59, 110, 59, 70, 80, 75, 84, 90, 80, 114, 100, 145, 123, 125, 81, 146, 68, 54, 37, ...
$ Price.value                 <dbl> 860.0000, 1.0000, 7.0000, 900.0000, 2.4000, 750.0000, 2.8000, 4.6000, 2.2000, 2.1000, 15.0000, 1.6000, 22.0000, 900.0000, 1.4000,...
$ Price.unit                  <fctr> Millions, Millions, Millions, Millions, Milliards, Millions, Milliards, Milliards, Milliards, Milliards, Millions, Millions, Mil...
$ Price.desc                  <fctr> Négociable, Négociable, Négociable, Offert, Négociable, Fixe, Négociable, Négociable, Négociable, Négociable, Négociable, Fixe, ...
$ Price.value.dzd             <dbl> 8600000, 10000000, 70000000, 9000000, 24000000, 7500000, 28000000, 46000000, 22000000, 21000000, 15000000, 16000000, 22000000, 90...
$ Price.rules                 <chr> "Price.value * 10000", "Price.value * 10000000", "Price.value * 10000000", "Price.value * 10000", "Price.value *10000000", "Price...
$ Announcer.Name              <chr> "youyayouya", "F123kniss", "megatrain21", "kikoum", "MS.Immobilier", "sidaeli1230", "APISERVCES", "APISERVCES", "APISERVCES", "AP...
$ Announcer.Type              <chr> "PARTICULIER", "PARTICULIER", "PARTICULIER", "PARTICULIER", "BUREAU D'AFFAIRE", "PARTICULIER", "BUREAU D'AFFAIRE", "BUREAU D'AFFA...
$ Hour                        <chr> "19", "19", "19", "21", "22", "16", "19", "20", "20", "20", "14", "19", "22", "20", "19", "22", "20", "20", "16", "19", "22", "20...
$ Month                       <ord> Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul, Jul,...
$ Municipality                <chr> "HAMMA ANASSERS", "BIRTOUTA", "DAR EL BEIDA", "DJISR KSENTINA", "BEB EZZOUAR", "BARAKI", "BIR MOURAD RAIS", "BIR MOURAD RAIS", "B...
$ Garage                      <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, NA, FALSE, NA, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, NA, FALSE, FALSE, FALSE,...
$ Garden                      <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, NA, FALSE, NA, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, NA, FALSE, FALSE, FALSE...
$ Furnished                   <lgl> FALSE, TRUE, FALSE, FALSE, FALSE, NA, FALSE, NA, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, NA, TRUE, FALSE, FALSE, ...
$ Promise                     <lgl> FALSE, FALSE, TRUE, FALSE, FALSE, NA, FALSE, NA, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, NA, FALSE, TRUE, FALSE, ...
$ New.Project                 <lgl> FALSE, FALSE, TRUE, FALSE, FALSE, NA, FALSE, NA, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, NA, FALSE, TRUE, FALSE, FA...
$ Payment                     <fctr> comptant, comptant, comptant, comptant, comptant, NA, comptant, NA, comptant, comptant, tranches, comptant, comptant, comptant, ...
$ Top_Municipalities          <fctr> Other, Other, Other, DJISR KSENTINA, BEB EZZOUAR, Other, Other, Other, Other, BIR KHADEM, Other, ALGER, CHERAGA, CHERAGA, Other,...
$ Store.Municipality          <chr> NA, NA, NA, NA, "BEB EZZOUAR", NA, "EL MADANIA", "EL MADANIA", "EL MADANIA", "EL MADANIA", NA, NA, "KOUBA", NA, "DAR EL BEIDA", "...
$ commune.names               <fctr> Algiers, HAMMA ANASSERS, Algiers, BIRTOUTA, Algiers, DAR EL BEIDA, Algiers, DJISR KSENTINA, Algiers, BEB EZZOUAR, Algiers, BARAK...
$ lon                         <dbl> 3.058756, 3.047605, 3.228198, 3.079263, 3.185497, 3.098580, 3.050374, 3.050374, 3.050374, 3.042598, 3.012567, 3.055116, 2.922589,...
$ lat                         <dbl> 36.75377, 36.64609, 36.70601, 36.69788, 36.72063, 36.66730, 36.73535, 36.73535, 36.73535, 36.71627, 36.78164, 36.77248, 36.76235,...
$ type                        <chr> "locality", "locality", "locality", "locality", "locality", "locality", "locality", "locality", "locality", "locality", "locality...
$ loctype                     <chr> "approximate", "approximate", "approximate", "approximate", "approximate", "approximate", "approximate", "approximate", "approxim...
$ address                     <chr> "algiers, sidi m'hamed, algeria", "birtouta, algeria", "dar el beïda, algeria", "djasr kasentina, algeria", "bab ezzouar, algeria...
$ north                       <dbl> 36.75878, 36.66857, 36.73901, 36.71377, 36.74174, 36.70469, 36.74079, 36.74079, 36.74079, 36.73351, 36.80971, 36.78404, 36.78475,...
$ south                       <dbl> 36.74887, 36.61635, 36.66587, 36.67178, 36.70393, 36.63556, 36.71975, 36.71975, 36.71975, 36.69576, 36.76851, 36.75229, 36.73448,...
$ east                        <dbl> 3.061624, 3.071880, 3.259807, 3.114774, 3.206227, 3.131125, 3.064499, 3.064499, 3.064499, 3.073656, 3.041807, 3.076917, 2.983024,...
$ west                        <dbl> 3.054058, 2.947340, 3.163548, 3.042440, 3.166648, 3.065958, 3.022013, 3.022013, 3.022013, 3.023633, 2.980932, 3.038953, 2.873654,...
$ locality                    <chr> "Algiers", "Birtouta", "Dar El Beïda", "Djasr Kasentina", "Bab Ezzouar", "Baraki", "Bir Mourad Raïs", "Bir Mourad Raïs", "Bir Mou...
$ locality.1                  <fctr> Sidi M'Hamed, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ administrative_area_level_1 <fctr> Algiers Province, Algiers Province, Algiers Province, Algiers Province, Algiers Province, Algiers Province, Algiers Province, Al...
$ country                     <fctr> Algeria, Algeria, Algeria, Algeria, Algeria, Algeria, Algeria, Algeria, Algeria, Algeria, Algeria, Algeria, Algeria, Algeria, Al...
$ political                   <fctr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, ...
$ Store.lon                   <dbl> NA, NA, NA, NA, 3.185497, NA, 3.068890, 3.068890, 3.068890, 3.068890, NA, NA, 3.081495, NA, 3.228198, 3.081495, 3.068890, NA, NA,...
$ Store.lat                   <dbl> NA, NA, NA, NA, 36.72063, NA, 36.74118, 36.74118, 36.74118, 36.74118, NA, NA, 36.72667, NA, 36.70601, 36.72667, 36.74118, NA, NA,...
LS0tDQp0aXRsZTogIkFsZ2VyaWFuIEhvdXNpbmcgUHJpY2UgQW5hbHlzaXMiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KDQpib2R5eyAvKiBOb3JtYWwgICovDQogICAgICBmb250LXNpemU6IDEwcHg7DQogIH0NCnRkIHsgIC8qIFRhYmxlICAqLw0KICBmb250LXNpemU6IDhweDsNCn0NCmgxLnRpdGxlIHsNCiAgZm9udC1zaXplOiAzOHB4Ow0KICBjb2xvcjogRGFya1JlZDsNCn0NCmgxIHsgLyogSGVhZGVyIDEgKi8NCiAgZm9udC1zaXplOiAyOHB4Ow0KICBjb2xvcjogRGFya0JsdWU7DQp9DQpoMiB7IC8qIEhlYWRlciAyICovDQogICAgZm9udC1zaXplOiAyMnB4Ow0KICBjb2xvcjogRGFya0JsdWU7DQp9DQpoMyB7IC8qIEhlYWRlciAzICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCn0NCmNvZGUucnsgLyogQ29kZSBibG9jayAqLw0KICAgIGZvbnQtc2l6ZTogMTBweDsNCn0NCnByZSB7IC8qIENvZGUgYmxvY2sgLSBkZXRlcm1pbmVzIGNvZGUgc3BhY2luZyBiZXR3ZWVuIGxpbmVzICovDQogICAgZm9udC1zaXplOiA4cHg7DQp9DQo8L3N0eWxlPg0KDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCiFbXShtZWV0dXBfbG9nby5qcGcpIA0KDQoNCjxicj48YnI+DQoNCiMjIyNUaGlzIGlzIG91ZWRrbmlzcy5jb20gdGhlIHRvcCBBbGdlcmlhbiBlLWNvbW1lcmNlIHdlYnNpdGUgd2hlcmUgdXN1YWxseSByZWFsIGVzdGF0ZSBhZ2VuY2llcyBhbmQgcHJvcGVydHkgb3duZXJzIGxpc3QgdGhlaXIgcHJvcGVydGllcyAgDQojIyMjV2Ugd2lsbCBzdGFydCBvdXIgam91cm5leSBieSBzY3JhcGluZyB0aGUgcHJvcGVydGllcyBsaXN0aW5nIGZvciBBbGdpZXJzIGZvciBvbmUgeWVhciBoaXN0b3J5IA0KIVtdKG91ZWRrbmlzc19tYXN0ZXJsaW5rLmdpZikNCg0KIVtdKG91ZWRrbmlzc19tYXN0ZXIuZ2lmKSAgDQoNCjxicj48YnI+DQoNCiMjIyNUbyBkbyB0aGlzIHdlIGFyZSBnb2luZyB0byB1c2UgcnZlc3QgbGlicmFyeSBmb3Igd2Vic2NyYXBpbmcgYW5kIHB1dXVyIHBhY2thZ2UgZm9yIGZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcgIA0KIyMjI0ZvciBhbGwgdGhlIHJlc3Qgd2UgYXJlIGdvaW5nIHRvIHVzZSB0aGUgdGlkeXZlcnNlIHBhY2thZ2VzICANCiMjIyNUaGUgdGlkeXZlcnNlIGlzIGFuIG9waW5pb25hdGVkIGNvbGxlY3Rpb24gb2YgUiBwYWNrYWdlcyBkZXNpZ25lZCBmb3IgZGF0YSBzY2llbmNlLiAgDQojIyMjQWxsIHBhY2thZ2VzIHNoYXJlIGFuIHVuZGVybHlpbmcgZGVzaWduIHBoaWxvc29waHksIGdyYW1tYXIsIGFuZCBkYXRhIHN0cnVjdHVyZXMgIA0KICANCiAgDQoNCg0KIVtdKHRpZHl2ZXJzZTEuZ2lmKQ0KDQohW10odGlkeXZlcnNlMi5naWYpDQoNCiFbXSh0aWR5dmVyc2UzLmdpZikNCg0KPGJyPjxicj4gIA0KDQojIyMjVGhlIGluZm9ybWF0aW9uIHRoYXQgd2UgYXJlIGxvb2tpbmcgZm9yIGlzIGhpZGVuIGludG8gdGhlIHdlYnNpdGUncyBodG1sLCB3ZSBhcmUgZ29pbmcgdG8gdXNlIENocm9tZSAgDQojIyMjU2VsZWN0b3JHYWRnZXQgZXh0ZW5zaW9uIHRvIGNhdGNoIHRoZSBDc3NTZWxlY3RvciBvZiB0aGUgZWxlbWVudHMgdGhhdCB3ZSB3YW50IHRvIGV4dHJhY3QuICANCiFbXShvdWVka25pc3NfZGV0YWlscy5naWYpDQoNCg0KPGJyPjxicj4gIA0KDQojIyMjbGV0J3MgZmlyc3QgaGF2ZSBhIGxvb2sgb24gaG93IHJ2ZXN0IGFuZCBwdXJyciB3b3JrICANCiAgDQogIA0KIVtdKFJ2ZXN0LmdpZikNCg0KDQo8YnI+PGJyPiAgDQoNCiMjIyMgUHVycnJyIGNoZWF0IHNoZWV0IGZyb20gUnN0dWRpbyAgDQohW10oUHVycnIxLmdpZikgDQoNCiFbXShQdXJycjIuZ2lmKQ0KDQo8YnI+PGJyPg0KPGJyPjxicj4NCiAgDQogIA0KIyMgTGV0J3Mgc3RhcnQgYnkgbG9hZGluZyB0aGUgbmVlZGVkIHBhY2thZ2VzDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKSAgICAgICAgICAgICAgI0ZvciBBbG1vc3QgZXZlcnl0aGluZw0KbGlicmFyeShydmVzdCkgICAgICAgICAgICAgICAgICAjRm9yIFdlYiBTY3JhcGluZw0KbGlicmFyeShEVCkgICAgICAgICAgICAgICAgICAgICAjRm9yIGZvcm1hdGluZyB0YWJsZXMgcGxvdHRpbmcgaW4gaHRtbA0KbGlicmFyeShsdWJyaWRhdGUpICAgICAgICAgICAgICAjRm9yIERhdGUgYW5kIFRpbWVzIG1hbmlwdWxhdGlvbg0KbGlicmFyeSh0aWN0b2MpICAgICAgICAgICAgICAgICAjRm9yIGNhbGN1bGF0aW5nIHRpbWUgZXhlY3V0aW9uIA0KbGlicmFyeShtYXB0b29scykgICAgICAgICAgICAgICAjRm9yIHJlYWRpbmcgc2hhcGUgZmlsZXMNCmxpYnJhcnkoZ2dtYXApICAgICAgICAgICAgICAgICAgI0dldCB0aGUgZ2VvbG9jYXRpb24NCmBgYA0KDQoNCiNTdGFydGluZyB0aGUgRGF0YSBTY3JhcGluZyB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30gIA0KDQojI3NjcmFwaW5nIG9uZSBwYWdlDQpgYGB7ciBmaWcud2lkdGg9MTZ9DQpTeXMuc2V0bG9jYWxlKCJMQ19DVFlQRSIsIkFyYWJpY19TYXVkaSBBcmFiaWEiKSAjY2hhbmdlIHRoZSBsb2NhbGUgc3lzdGVtIHRvIGdldCBwcm9wZXIgYXJhYmljIA0KDQojIFN0b3JlIHdlYiB1cmwNCmltbW9fdXJsIDwtICgiaHR0cHM6Ly93d3cub3VlZGtuaXNzLmNvbS9hbm5vbmNlcy9pbmRleC5waHA/Yz1pbW1vYmlsaWVyJnNjPXZlbnRlJnNjMj1hcHBhcnRlbWVudCZ3aWxheWE9JTJDYWxnZXImcHJpeD0xJnByaXhfdW5pdGU9MiZwPTEiKQ0KDQppbW1vX3VybA0KDQpMaW5rcyA8LSBpbW1vX3VybCAlPiUgcmVhZF9odG1sKC4sZW5jb2RpbmcgPSAiVVRGLTgiKSAlPiUgICAgICAgICAgI1JlYWQgdGhlIGh0bWwgb2YgdGhlIHVybA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGh0bWxfbm9kZXMoIi5idXR0b25fZGV0YWlscyIpICU+JSAgICNTZWxlY3QgYnV0dG9uX2RldGFpbHMgZWxlbWVudA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGh0bWxfYXR0cigiaHJlZiIpICAgICAgICAgICAgICAgICAgICNFeHRyYWN0IGFsbCBoeXBlcmxpbmtzIHRleHQgICAgICAgDQoNCkxpbmtzICMgcHJpbnQgdGhlIGNvbnRlbnQgb2YgTGlua3MNCg0KTGlua3MgPC0gTGlua3MgJT4lDQogICAgICAgIHVubGlzdCgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI3RyYW5zZm9ybSBMaW5rcyB0byBhIHZlY3Rvcg0KICAgICAgICBwYXN0ZSgiaHR0cHM6Ly93d3cub3VlZGtuaXNzLmNvbS8iLCAuLCBzZXAgPSAiIikgICAgICAgICAgICNjb25jYXRlbmF0ZSB0aGUgd2Vic2l0ZSByb290IGFkZHJlc3Mgd2l0aCB0aGUgbGlua3MNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQoNCkxpbmtzDQoNCiMgd2Ugd2lsbCBzaG93ICBob3cgaXQgd29ya3MgZm9yIG9uZSBsaW5rDQoNClBhZ2UgPC0gIHJlYWRfaHRtbChMaW5rc1sxXSxlbmNvZGluZyA9ICJVVEYtOCIpIA0KDQpQYWdlICNwcmludCB0aGUgY29udGVudCBvZiBQYWdlDQoNCg0KDQojTGV0J3MgZXh0cmFjdCB0aGUgVGl0bGUNClRpdGxlIDwtIFBhZ2UgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICNJcyBvdXIgSHRtbCBkb2N1bWVudA0KICAgICAgICAgaHRtbF9ub2RlcygiI1RpdGxlIikgJT4lICAgICAgICAgICAgICAgICAgICAgICAgI1dlIGFyZSBzZWxlY3RpbmcgdGhlIFRpdGxlIGVsZW1lbnQgDQogICAgICAgICBodG1sX3RleHQoKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjRXh0cmFjdGluZyB0aGUgdGV4dCBmcm9tIHRoZSBUaXRsZSBlbGVtZW50IGluIHRoZSBIdG1sIGRvY3VtZW50DQogICAgICAgIA0KVGl0bGUNCg0KDQojR2V0dGluZyB0aGUgRGVzY3JpcHRpb24NCg0KRGVzY3JpcHRpb24gPC0gUGFnZSAlPiUgDQogICAgICAgIGh0bWxfbm9kZXMoIiNHZXREZXNjcmlwdGlvbiIpICU+JSANCiAgICAgICAgaHRtbF90ZXh0KCkNCiAgICAgICAgICAgIA0KRGVzY3JpcHRpb24NCg0KDQojR2V0aW5nIHRoZSBQcmljZSB3aXRoIHRoZSBkZXRhaWxzDQoNCkRldGFpbHMgPC0gUGFnZSAlPiUgDQogICAgICAgIGh0bWxfbm9kZXMoIiNQcml4LCNEZXNjcmlwdGlvbiBwIikgJT4lIA0KICAgICAgICBodG1sX3RleHQoKQ0KRGV0YWlscw0KDQoNCg0KI0dldHRpbmcgdGhlIHN0b3JlIG5hbWUNClN0b3JlLk5hbWUgPC0gIGh0bWxfbm9kZXMoUGFnZSwgIiNzdG9yZV9uYW1lIikgJT4lICAgICAgI1llcywgaXQgY2FuIGJlIHdyaXR0ZW4gYWxzbyBsaWtlIHRoaXMNCiAgICAgICAgaHRtbF90ZXh0KCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgDQpTdG9yZS5OYW1lDQoNCiNHZXR0aW5nIHRoZSBzdG9yZSBhZGRyZXNzDQpTdG9yZS5BZGRyZXNzZTwtIFBhZ2UgJT4lIA0KICAgICAgICBodG1sX25vZGVzKCIjc3RvcmVfYWRyZXNzZSIpICU+JSANCiAgICAgICAgaHRtbF90ZXh0KCkNCg0KU3RvcmUuQWRkcmVzc2UgDQoNCg0KI0dldHRpbmcgdGhlIHBzZXVkbyBmb3IgdGhlIG93bmVycw0KDQpQc2V1ZG8gPC0gUGFnZSAlPiUgDQogICAgICAgIGh0bWxfbm9kZXMoIi5Qc2V1ZG8iKSAlPiUgDQogICAgICAgIGh0bWxfdGV4dCgpDQoNClBzZXVkbyA8LSBOQQ0KDQojTm93IHdlIG5lZWQgdG8gZ3JvdXAgYWxsIHRoZSBlbGVtZW50cyBleHRyYWN0ZWQgaW4gb25lIHRhYmxlICANCiNXZSBoYXZlIGRpZmZlcmVudCB0dHlwZSBvZiBlbGVtZW50cyB3aXRoIGRpZmZlcmVudCBsZW5ndGgNCm1hcF9kYmwobGlzdChMaW5rc1sxXSxUaXRsZSwgRGVzY3JpcHRpb24sRGV0YWlscyxTdG9yZS5OYW1lLCBQc2V1ZG8gKSwgbGVuZ3RoKQ0KI3dlIGNhbiB1c2UgYSBsaXN0IHRvIGdyb3VwIGFsbCBlbGVtZW50cyBidXQgYmV0dGVyIGlzIHRvIHVzZSBhIHRpYmJsZSwgYSB0aWJibGUgaXMgYSBuZXcgZW5oYW5jZWQgZGF0YSBmcmFtZSBzdHJ1Y3R1cmUgIA0KI3RoYXQgYWNjZXB0cyB0aGUgY29sdW1ucyBsaXN0DQoNCg0Kb25lX2xpbmtfZGF0YSA8LSB0aWJibGUoTGlua3NbMV0sVGl0bGUsIERlc2NyaXB0aW9uLERldGFpbHMsU3RvcmUuTmFtZSwgUHNldWRvKSANCm9uZV9saW5rX2RhdGENCg0KIyBSZXBsYWNlIGVtcHR5IGRldGFpbHMgd2l0aCBOQQ0Kb25lX2xpbmtfZGF0YSA8LSAgIG9uZV9saW5rX2RhdGEgJT4lDQogICAgICAgICAgICAgICBtdXRhdGUoVGl0bGUgPSBhcy5jaGFyYWN0ZXIoaWZlbHNlKFRpdGxlPT0iY2hhcmFjdGVyKDApIiwgTkEsIFRpdGxlKSksDQogICAgICAgICAgICAgICBEZXNjcmlwdGlvbiA9IGFzLmNoYXJhY3RlcihpZmVsc2UoRGVzY3JpcHRpb249PSJjaGFyYWN0ZXIoMCkiLCBOQSwgRGVzY3JpcHRpb24pKSwNCiAgICAgICAgICAgICAgIERldGFpbHMgPSBpZmVsc2UoRGV0YWlscz09ImNoYXJhY3RlcigwKSIsIE5BLCBEZXRhaWxzKSwNCiAgICAgICAgICAgICAgIFN0b3JlLk5hbWUgPSBhcy5jaGFyYWN0ZXIoaWZlbHNlKFN0b3JlLk5hbWU9PSJjaGFyYWN0ZXIoMCkiLCBOQSwgU3RvcmUuTmFtZSkpLA0KICAgICAgICAgICAgICAgU3RvcmUuQWRkcmVzc2UgPSBhcy5jaGFyYWN0ZXIoaWZlbHNlKFN0b3JlLkFkZHJlc3NlPT0iY2hhcmFjdGVyKDApIiwgTkEsIFN0b3JlLkFkZHJlc3NlKSksDQogICAgICAgICAgICAgICBQc2V1ZG8gPSBhcy5jaGFyYWN0ZXIoaWZlbHNlKFBzZXVkbz09ImNoYXJhY3RlcigwKSIsIE5BLCBQc2V1ZG8pKSwNCiAgICAgICAgICAgICAgICMgRXh0cmFjdCBkZXRhaWxzIGVsZW1lbnRzIGFuZCB2YWx1ZXMgZnJvbSBwb3N0IERlc2NyaXB0aW9uDQogICAgICAgICAgICAgICBEZXQgPSBtYXAoRGV0YWlscywgZnVuY3Rpb24oeCkgc3RyX3RyaW0oc3RyX3NwbGl0X2ZpeGVkKHN0cmluZyA9IHhbXSwgcGF0dGVybiA9ICIgOiAiLCBuPTIpWywxXSkpLA0KICAgICAgICAgICAgICAgVmFsdWVzID0gbWFwKERldGFpbHMsIGZ1bmN0aW9uKHgpIHN0cl90cmltKHN0cl9zcGxpdF9maXhlZChzdHJpbmcgPSB4W10sIHBhdHRlcm4gPSAiIDogIiwgbj0yKVssMl0pKQ0KICAgICAgICAgICAgICAgIyBEZXQgYW5kIFZhbHVlcyBhcmUgbGlzdCBjb2x1bW5zICwgd2UgaGF2ZSBuZXN0ZWQgZGF0YSBmcmFtZSBub3cNCiAgICAgICAgICAgICAgICkgJT4lDQogICAgICAgIHNlbGVjdCgtRGV0YWlscykNCm9uZV9saW5rX2RhdGENCg0KIyBHZXQgcmlkIG9mIG5lc3RlZCBjb2x1bW5zIGJ5IHVubmVzdGluZyB0aGUgZGF0YQ0Kb25lX2xpbmtfZGF0YSA8LSBvbmVfbGlua19kYXRhICU+JSAgdW5uZXN0KCkgDQpvbmVfbGlua19kYXRhDQojIERvIHNvbWUgY2xlYW5zaW5nDQpvbmVfbGlua19kYXRhIDwtIG9uZV9saW5rX2RhdGEgJT4lDQogICAgICAgIGZpbHRlcihEZXQgIT0gIiIpJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyByZW1vdmUgd2hpdGUgc3BhY2UNCiAgICAgICAgbXV0YXRlKERldCA9ICBjYXNlX3doZW4oICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHJlcGxhY2Ugd2l0aCBjb3JyZWN0IHNwZWxsaW5nDQogICAgICAgICAgICAgICAgLiREZXQgPT0gIk5vbWJyZSBkZSBwacODwqhjZXMiIH4gIk5vbWJyZSBkZSBwacOoY2VzIiwNCiAgICAgICAgICAgICAgICAuJERldCA9PSAiU3DDg8KpY2lmaWNhdGlvbnMiIH4gIlNww6ljaWZpY2F0aW9ucyIsDQogICAgICAgICAgICAgICAgLiREZXQgPT0gIk5vbWJyZSBkJ8ODwql0YWdlcyAvIMODwql0YWdlIiB+ICJOb21icmUgZCfDqXRhZ2VzIC8gw6l0YWdlIiwNCiAgICAgICAgICAgICAgICAuJERldCA9PSAiRMODwqlwb3PDg8KpZSBsZSIgfiAiRMOpcG9zw6llIGxlIiwNCiAgICAgICAgICAgICAgICAuJERldCA9PSAiTnVtw4PCqXJvIiB+ICJOdW3DqXJvIiwNCiAgICAgICAgICAgICAgICBUUlVFIH4gYXMuY2hhcmFjdGVyKC4kRGV0KQ0KICAgICAgICAgICAgICAgICkpICU+JSANCiAgICAgICAgI1RoZSBuZXh0IHRocmVlIGxpbmVzIHRvIHJlbW92ZSBzb21lIGR1cGxpY2F0ZXMNCiAgICAgICAgI0Nob29zZSB0aGUgcmVjb3JkIHdpdGggdGhlIGhpZ2hlc3QgVmFsdWVzIGFuZCBkcm9wIHRoZSBvdGhlciBkdXBsaWNhdGVzDQogICAgICAgIGdyb3VwX2J5KExpbmtzWzFdLCBEZXQpICU+JQ0KICAgICAgICBhcnJhbmdlKGRlc2MoVmFsdWVzKSkgJT4lDQogICAgICAgIHNsaWNlKDEpIA0Kb25lX2xpbmtfZGF0YQ0KDQoNCiNUaWR5aW5nIHRoZSBkYXRhLCBub3cgd2Ugd2lsbCBoYXZlIDEgcm93IHBlciBob3VzaW5nIGxpc3RpbmcgYW5kIGFsbCBvdGhlciBkZXRhaWxzIG9uIGNvbHVtbnMNCm9uZV9saW5rX2RhdGEgPC0gb25lX2xpbmtfZGF0YSAlPiUgc3ByZWFkKERldCx2YWx1ZSA9IFZhbHVlcykgDQpvbmVfbGlua19kYXRhDQogICAgICAgICAgICAgICAgDQpgYGANCg0KDQojIyBDcmVhdGluZyB0aGUgc2NyYXBlciBmdW5jdGlvbiANCg0KYGBge3IsIGV2YWw9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzY3JhcGVfaG91c2VzIDwtIGZ1bmN0aW9uKG5iLnBhZ2UgPSAxKXsNCg0KU3lzLnNldGxvY2FsZSgiTENfQ1RZUEUiLCJBcmFiaWNfU2F1ZGkgQXJhYmlhIikgI2NoYW5nZSB0aGUgbG9jYWxlIHN5c3RlbSB0byBnZXQgcHJvcGVyIGFyYWJpYyB0ZXh0DQoNCiMgU3RvcmUgd2ViIHVybA0KaW1tb191cmwgPC0gKCJodHRwczovL3d3dy5vdWVka25pc3MuY29tL2Fubm9uY2VzL2luZGV4LnBocD9jPWltbW9iaWxpZXImc2M9dmVudGUmc2MyPWFwcGFydGVtZW50JndpbGF5YT0lMkNhbGdlciZwcml4PTEmcHJpeF91bml0ZT0yJnA9IikgJT4lDQogICAgICAgICAgICAgcGFzdGUgKC4gLCBuYi5wYWdlLCBzZXAgPSAiIikNCg0KIyBTY3JhcGUgdGhlIHBvc3QgbGlua3MgZnJvbSB0aGUgcGFnZSB1cmxzDQoNCiNHZXR0aW5nIGFsbCB0aGUgbGlua3Mgb2YgdGhlIHJvb3QgcGFnZSBmcm9tIHRoZSBkZXRhaWxzIGJ1dHRvbg0KI1dlIGFyZSBnb2luZyB0byB1c2UgcG9zc2libHkgd2hpY2ggcmVwbGFjZSBhbnkgZXJyb3JzIGluIHRoZSBzY3JhcGluZyB3aXRoIGEgbGlnbiBvZiBOQSdzLCBpbiBvcmRlciB0byBhdm9pZCAgDQojdGhlIGVhcmx5IHN0b3BwaW5nIG9mIHRoZSBzY3JhcGluZyBkdWUgdG8gdGhlIGVycm9ycw0KdGljKCJsaW5rcyIpICMgRW5jYXBzdWxpbmcgZWFjaCBzdGVwIGJld3RlZW4gdGljIHRvYyBjb21tYW5kZXMgdG8gY2FsY3VsYXRlIHRoZSBleGVjdXRpb24gdGltZQ0KDQpMaW5rcyA8LSBtYXAoaW1tb191cmwsIHBvc3NpYmx5KC4lPiVyZWFkX2h0bWwoLixlbmNvZGluZyA9ICJVVEYtOCIpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBodG1sX25vZGVzKCIuYnV0dG9uX2RldGFpbHMiKSAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaHRtbF9hdHRyKCJocmVmIikNCiAgICAgICAgICAgICAsTkFfcmVhbF8pKSAlPiUNCiAgICAgICAgdW5saXN0KCkgJT4lIA0KICAgICAgICBwYXN0ZSgiaHR0cHM6Ly93d3cub3VlZGtuaXNzLmNvbS8iLCAuLCBzZXAgPSAiIikNCnRvYygpDQoNCg0KdGljKCJwYWdlcyIpDQojR2V0dGluZyBhbGwgdGhlIHBhZ2VzIG9mIHRoZSBsaW5rcyANCiNJbiBvcmRlciB0byBkbyBpdCBmb3IgYWxsIHRoYSBsaW5rcyB3ZSBuZWVkIHRvIHVzZSBtYXAgZnVuY3Rpb24gDQpQYWdlcyA8LSBtYXAoTGlua3MsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjRmlyc3QgYXJndW1lbnQgb2YgdGhlIG1hcCBmdW5jdGlvbiBpcyBvdXIgZGF0YQ0KICAgICAgICAgICAgIHBvc3NpYmx5KCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBXZSBhcmUgdXNpbmcgcG9zc2libHkgdG8gY2hhbmdlIHRoZSBiZWhhdmlvciBvZiBhbnkgZXJyb3IgbWVzc2FnZQ0KICAgICAgICAgICAgICAgICAgICAgcmVhZF9odG1sKC4sZW5jb2RpbmcgPSAiVVRGLTgiKSAgLCAgI3RoZSBzZWNvbmQgYXJndW1lbnQgb2YgdGhlIG1hcCBmdW5jdGlvbiBpcyB0aGUgZnVuY3Rpb24gdGhhdCB3ZSB3YW50IHRvIGFwcGx5DQogICAgICAgICAgICAgICAgICAgICAgTkFfcmVhbF8pKSAgICAgICAgICAgICAgICAgICAgICAgICAjV2UgYXJlIGdvaW5nIHRvIGZpbGwgb3VyIGRhdGEgd2l0aCBOQSdzIGlmIGFueSBlcnJvciBvY3V1cg0KDQp0b2MoKQ0KDQojIEdldHRpbmcgIHRoZSBwb3N0IHRpdGxlDQp0aWMoInRpdGxlcyIpDQpUaXRsZSA8LSBtYXAoUGFnZXMscG9zc2libHkoLiAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgaHRtbF9ub2RlcygiI1RpdGxlIikgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGh0bWxfdGV4dCgpDQogICAgICAgICAgICAgICAgICxOQV9yZWFsXykpDQp0b2MoKQ0KDQojR2V0dGluZyB0aGUgRGVzY3JpcHRpb24NCnRpYygiZGVzY3JpcHRpb24iKQ0KRGVzY3JpcHRpb24gPC0gbWFwKFBhZ2VzLHBvc3NpYmx5KC4gJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGh0bWxfbm9kZXMoIiNHZXREZXNjcmlwdGlvbiIpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICBodG1sX3RleHQoKQ0KICAgICAgICAgICAgICAgICAsTkFfcmVhbF8pKQ0KdG9jKCkNCg0KI0dldGluZyB0aGUgUHJpY2Ugd2l0aCB0aGUgZGV0YWlscw0KdGljKCJkZXRhaWxzIikNCkRldGFpbHMgPC0gbWFwKFBhZ2VzLHBvc3NpYmx5KC4gJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGh0bWxfbm9kZXMoIiNQcml4LCNEZXNjcmlwdGlvbiBwIikgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGh0bWxfdGV4dCgpDQogICAgICAgICAgICAgICAgICxOQV9yZWFsXykpDQp0b2MoKQ0KDQojR2V0dGluZyB0aGUgc3RvcmUgbmFtZQ0KdGljKCJzdG9yZS5uYW1lIikNClN0b3JlLk5hbWU8LSBtYXAoUGFnZXMscG9zc2libHkoLiAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgaHRtbF9ub2RlcygiI3N0b3JlX25hbWUiKSAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgaHRtbF90ZXh0KCkNCiAgICAgICAgICAgICAgICAgLE5BX3JlYWxfKSkgDQp0b2MoKQ0KDQojR2V0dGluZyB0aGUgc3RvcmUgYWRkcmVzcw0KdGljKCJzdG9yZS5hZGRyZXNzZSIpDQpTdG9yZS5BZGRyZXNzZTwtIG1hcChQYWdlcyxwb3NzaWJseSguICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICBodG1sX25vZGVzKCIjc3RvcmVfYWRyZXNzZSIpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICBodG1sX3RleHQoKQ0KICAgICAgICAgICAgICAgICAgLE5BX3JlYWxfKSkNCnRvYygpDQoNCiNHZXR0aW5nIHRoZSBwc2V1ZG8gZm9yIHRoZSBvd25lcnMNCnRpYygicHNldWRvIikNClBzZXVkbyA8LSBtYXAoUGFnZXMscG9zc2libHkoLiAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgaHRtbF9ub2RlcygiLlBzZXVkbyIpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICBodG1sX3RleHQoKQ0KICAgICAgICAgICAgICAsTkFfcmVhbF8pKSANCnRvYygpIA0KDQojIGFkZGluZyBldmVyeXRoaW5nIHRvIGEgdGliYmxlIGFuZCByZXNoYXBpbmcgdGhlIGRhdGEgDQp0aWMoImNvbnN0cnVjdGluZyB0aWJibGUiKSAgICAgICAgDQpob3VzaW5nX2RhdGEgPC0gdGliYmxlKExpbmtzLFRpdGxlLCBEZXNjcmlwdGlvbixEZXRhaWxzLFN0b3JlLk5hbWUsIFBzZXVkbykgJT4lICANCiAgICAgICAgIyByZXBsYWNlIGVtcHR5IGRldGFpbHMgd2l0aCBOQQ0KICAgICAgICBtdXRhdGUoVGl0bGUgPSBhcy5jaGFyYWN0ZXIoaWZlbHNlKFRpdGxlPT0iY2hhcmFjdGVyKDApIiwgTkEsIFRpdGxlKSksDQogICAgICAgICAgICAgICBEZXNjcmlwdGlvbiA9IGFzLmNoYXJhY3RlcihpZmVsc2UoRGVzY3JpcHRpb249PSJjaGFyYWN0ZXIoMCkiLCBOQSwgRGVzY3JpcHRpb24pKSwNCiAgICAgICAgICAgICAgIERldGFpbHMgPSBpZmVsc2UoRGV0YWlscz09ImNoYXJhY3RlcigwKSIsIE5BLCBEZXRhaWxzKSwNCiAgICAgICAgICAgICAgIFN0b3JlLk5hbWUgPSBhcy5jaGFyYWN0ZXIoaWZlbHNlKFN0b3JlLk5hbWU9PSJjaGFyYWN0ZXIoMCkiLCBOQSwgU3RvcmUuTmFtZSkpLA0KICAgICAgICAgICAgICAgU3RvcmUuQWRkcmVzc2UgPSBhcy5jaGFyYWN0ZXIoaWZlbHNlKFN0b3JlLkFkZHJlc3NlPT0iY2hhcmFjdGVyKDApIiwgTkEsIFN0b3JlLkFkZHJlc3NlKSksDQogICAgICAgICAgICAgICBQc2V1ZG8gPSBhcy5jaGFyYWN0ZXIoaWZlbHNlKFBzZXVkbz09ImNoYXJhY3RlcigwKSIsIE5BLCBQc2V1ZG8pKSwNCiAgICAgICAgICAgICAgICMgRXh0cmFjdCBkZXRhaWxzIGVsZW1lbnRzIGFuZCB2YWx1ZXMgZnJvbSBwb3N0IERlc2NyaXB0aW9uDQogICAgICAgICAgICAgICBEZXQgPSBtYXAoRGV0YWlscywgZnVuY3Rpb24oeCkgc3RyX3RyaW0oc3RyX3NwbGl0X2ZpeGVkKHN0cmluZyA9IHhbXSwgcGF0dGVybiA9ICIgOiAiLCBuPTIpWywxXSkpLA0KICAgICAgICAgICAgICAgVmFsdWVzID0gbWFwKERldGFpbHMsIGZ1bmN0aW9uKHgpIHN0cl90cmltKHN0cl9zcGxpdF9maXhlZChzdHJpbmcgPSB4W10sIHBhdHRlcm4gPSAiIDogIiwgbj0yKVssMl0pKQ0KICAgICAgICAgICAgICAgIyBEZXQgYW5kIFZhbHVlcyBhcmUgbGlzdCBjb2x1bW5zICwgd2UgaGF2ZSBuZXN0ZWQgZGF0YSBmcmFtZSBub3cNCiAgICAgICAgICAgICAgICkgJT4lDQogICAgICAgIHNlbGVjdCgtRGV0YWlscykgJT4lDQogICAgICAgICMgR2V0IHJpZCBvZiBuZXN0ZWQgY29sdW1ucyBieSB1bm5lc3RpbmcgdGhlIGRhdGENCiAgICAgICAgdW5uZXN0KCkgJT4lDQogICAgICAgICMgRG8gc29tZSBjbGVhbnNpbmcNCiAgICAgICAgZmlsdGVyKERldCAhPSAiIiklPiUNCiAgICAgICAgbXV0YXRlKERldCA9ICBjYXNlX3doZW4oDQogICAgICAgICAgICAgICAgLiREZXQgPT0gIk5vbWJyZSBkZSBwacODwqhjZXMiIH4gIk5vbWJyZSBkZSBwacOoY2VzIiwNCiAgICAgICAgICAgICAgICAuJERldCA9PSAiU3DDg8KpY2lmaWNhdGlvbnMiIH4gIlNww6ljaWZpY2F0aW9ucyIsDQogICAgICAgICAgICAgICAgLiREZXQgPT0gIk5vbWJyZSBkJ8ODwql0YWdlcyAvIMODwql0YWdlIiB+ICJOb21icmUgZCfDqXRhZ2VzIC8gw6l0YWdlIiwNCiAgICAgICAgICAgICAgICAuJERldCA9PSAiRMODwqlwb3PDg8KpZSBsZSIgfiAiRMOpcG9zw6llIGxlIiwNCiAgICAgICAgICAgICAgICAuJERldCA9PSAiTnVtw4PCqXJvIiB+ICJOdW3DqXJvIiwNCiAgICAgICAgICAgICAgICBUUlVFIH4gYXMuY2hhcmFjdGVyKC4kRGV0KQ0KICAgICAgICAgICAgICAgICkpICU+JSANCiAgICAgICAgIyB0aGUgbmV4dCB0aHJlZSBsaW5lcyB0byByZW1vdmUgc29tZSBkdXBsaWNhdGVzDQogICAgICAgIGdyb3VwX2J5KExpbmtzLCBEZXQpICU+JQ0KICAgICAgICBhcnJhbmdlKGRlc2MoVmFsdWVzKSkgJT4lDQogICAgICAgIHNsaWNlKDEpICU+JSAjIGNob29zZSB0aGUgcmVjb3JkIHdpdGggdGhlIGhpZ2hlc3QgVmFsdWVzIGFuZCBkcm9wIHRoZSBvdGhlciBkdXBsaWNhdGVzDQogICAgICAgICMgdGlkeWluZyB0aGUgZGF0YSwgbm93IHdlIHdpbGwgaGF2ZSAxIHJvdyBwZXIgaG91c2luZyBsaXN0aW5nIGFuZCBhbGwgb3RoZXIgZGV0YWlscyBvbiBjb2x1bW5zDQogICAgICAgIHNwcmVhZChEZXQsdmFsdWUgPSBWYWx1ZXMpIA0KIHRvYygpDQogdGljKCJyZXR1cm4iKQ0KcmV0dXJuKGhvdXNpbmdfZGF0YSkNCiB0b2MoKQ0KfQ0KYGBgDQojDQojIyBTY3JhcGUgdGhlIGhvdXNpbmcgZGF0YSBhbmQgc2F2ZSBpdCB0byB0aGUgZGlzaw0KDQpgYGB7cn0NCiNDcmVhdGUgYSBnZW50bGUgc2NyYXBpbmcgZnVuY3Rpb24NCkFzYmVyX0Nob3VpYSA8LSBmdW5jdGlvbiAocGVyaW9kcyA9IGMoMSwxLjUpKSB7DQogICAgICAgIFNsZWVwQ2FsbHMgPC0gIHJ1bmlmKDEsIHBlcmlvZHNbMV0scGVyaW9kc1syXSkgIyBnZW5lcmF0ZSBhIHVuaWZvcm0gcmFuZG9tIHZhbHVlICBiZXR3ZWVuIHBlcmlvZCAxIGFuZCBwZXJpb2QgMg0KICAgICAgICAjIHNvbWUgcHJpbml0bmcgdG8gc2VwZXJhdGUgdGhlIGV4ZWN1dGlvbg0KICAgICAgICBjYXQocGFzdGUoIi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0iLCIiLHNlcCA9ICJcbiIpKSANCiAgICAgICAgY2F0KHBhc3RlMChTeXMudGltZSgpKSwiUmFuaSBTYWJlciIsIHJvdW5kKFNsZWVwQ2FsbHMsMiksICJTZWNvbmRzXG4iKQ0KICAgICAgICBjYXQocGFzdGUoIiIsIiIsIHNlcCA9ICJcbiIpKQ0KICAgICAgICBTeXMuc2xlZXAoU2xlZXBDYWxscykgI0NhdXNlIHRoZSBzeXRlbSB0byBzbGVlcCBiZWZvcmUgY29udGludWUgdGhlIHNjcmlwdCBleGVjdXRpb24NCn0NCg0KI1dyYXAgZXZlcnl0aGluZyBpbnRvIGEgZnVuY3Rpb24NCkdlbnRsZVNjcmFwaW5nIDwtIGZ1bmN0aW9uKFN0YXJ0X1NsZWVwPTEsIEZpbmlzaF9TbGVlcD0xLjUsIFBhZ2Upew0KICAgICAgICBBc2Jlcl9DaG91aWEgKGMoU3RhcnRfU2xlZXAsIEZpbmlzaF9TbGVlcCkpDQogICAgICAgIEFsZ2llcnNfU2FsZXNBcHBhcnRtZW50cyA8LSBzY3JhcGVfaG91c2VzKFBhZ2UpDQogICAgICAgIHJldHVybihBbGdpZXJzX1NhbGVzQXBwYXJ0bWVudHMpDQp9DQoNCg0Kc3RhcnR0aW1lPC0gU3lzLnRpbWUoKSAjIFRvIGNvdW50IGV4ZWN1dGlvbiB0aW1lDQojQXBwbHkgdGhlIHNjcmFwaW5nIGZ1bmN0aW9uIGZvciAxMDAgcGFnZXMgKHRoYXQgY29ycmVzcG9uZCB0byBvbmUgeWVhciBsaXN0aW5nKQ0KZGF0YV9zY3JhcGVkIDwtICB2ZWN0b3IoImxpc3QiLDEwMCkgICNGaXJzdCBjcmF0ZSBhbiBlbXB0eSBsaXN0IG9mIDEwMCBlbGVtZW50DQpmb3IgKGkgaW4gMToxMDApDQp7DQogICAgICAgIGNhdChwYXN0ZSgiIiwiIiwgc2VwID0gIlxuIikpDQogICAgICAgIGNhdChwYXN0ZTAoIlNjcmFwaW5nIFBhZ2UiLCIgIixpKSkNCiAgICAgICAgY2F0KHBhc3RlKCIiLCIiLCBzZXAgPSAiXG4iKSkNCiAgICAgICAgIyBpdGVyYXRlIHRocm91Z2ggIGFsbCB0aGUgcGFnZXMsIGluIGVhY2ggcGFnZSB0aGVyZSBpcyAzMCBsaW5rcyB0byBsaXN0aW5nIGhvdXNlcyAgDQogICAgICAgICMgZm9yIGVhY2ggMzAgbGlua3Mgc2NyYXBlZCB0aGUgc2NyYXBpbmcgd2lsbCBwYXVzZSBmb3IgYSBwZXJpb2Qgb2YgZmV3IHNlY29uZHMgIA0KICAgICAgICAjIGJlZm9yZSBjb250aW51aW5nIHRoZSBzY3JhcGluZw0KICAgICAgICBkYXRhX3NjcmFwZWRbW2ldXTwtIEdlbnRsZVNjcmFwaW5nIChTdGFydF9TbGVlcCA9MSAsRmluaXNoX1NsZWVwID0xLjUgLFBhZ2UgPSBpKSAgIyBpdGVyYXRlIHRocm91Z2ggICAgDQp9DQplbmR0aW1lPC0gU3lzLnRpbWUoKQ0KZW5kdGltZSAtIHN0YXJ0dGltZSAjIEdpdmUgdGhlIHNjcmFwaW5nIHRpbWUNCg0KQWxnaWVyc19TYWxlQXBwYXJ0bWVudCA8LSBiaW5kX3Jvd3MoZGF0YV9zY3JhcGVkKSAjIGFwcGVuZCBhbGwgdGhlIGVsZW1lbnQgb2YgdGhlIGxpc3QgaW50byBvbmUgYmlnIGRhdGEgZnJhbWUNCiNTYXZlIHRoZSBkYXRhIHRvIHRoZSBkaXNrDQp3cml0ZS5jc3YoQWxnaWVyc19TYWxlQXBwYXJ0bWVudCwgIkFsZ2llcnNfU2FsZXNBcHBhcnRtZW50czI2MDcyMDE4LmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFLCBmaWxlRW5jb2RpbmcgPSAiVVRGLTgiKSANCmBgYA0KIyBEYXRhIFByZXBhcmF0aW9uIGFuZCBjbGVhbnNpbmcNCmBgYHtyIGVjaG89VFJVRX0NCmhvdXNpbmcgPC0gcmVhZC5jc3YoIkFsZ2llcnNfU2FsZXNBcHBhcnRtZW50czI2MDcyMDE4LmNzdiIsIGZpbGVFbmNvZGluZyA9ICJVVEYtOCIpICMjIyBSZWFkaW5nIHRoZSBkYXRhIA0KbmFtZXMoaG91c2luZykgWzc6MTVdPC0gYygiRGF0ZSIsICJOYi5GbG9vciIsICJOYi5Sb29tIiwgIk5iLlZpZXdzIiwgIklELk9mZmVyIiwgIlByaWNlIiwgIkRpc3RyaWN0IiwgIlNwZWNpZmljcyIsICJBcmVhIikgIyMjIFJlbmFtZSBzb21lIHZhcmlhYmxlcw0KDQoNCiNDcmVhdGUgc29tZSBuZXcgdmFyaWFibGVzIGJhc2VkIG9uIHRoZSBpbmZvcm1hdGlvbiBmb3VuZCBvbiB0aGUgZXhpc3Rpbmcgb25lcw0KaG91c2luZyAgPC0gaG91c2luZyAlPiUNCiAgICAgICAgbXV0YXRlKFByaWNlLnZhbHVlID0gYXMubnVtZXJpYyhzdHJfc3BsaXRfZml4ZWQoc3RyX3RyaW0oaG91c2luZyRQcmljZSksICIgIiwzKVssMV0pLA0KICAgICAgICAgICAgICAgUHJpY2UudW5pdD0gZmFjdG9yKHN0cl9zcGxpdF9maXhlZChzdHJfdHJpbShob3VzaW5nJFByaWNlKSwgIiAiLDMpWywyXSksDQogICAgICAgICAgICAgICBQcmljZS5kZXNjPSBmYWN0b3Ioc3RyX3NwbGl0X2ZpeGVkKHN0cl90cmltKGhvdXNpbmckUHJpY2UpLCAiICIsMylbLDNdKSwNCiAgICAgICAgICAgICAgIEFyZWEgPSBhcy5udW1lcmljKHN0cl9zcGxpdF9maXhlZChzdHJfdHJpbShob3VzaW5nJEFyZWEpLCAiICIsMilbLDFdKQ0KICAgICAgICApICU+JQ0KICAgICAgICBtdXRhdGUoUHJpY2UudmFsdWUuZHpkID0gIyBDcmVhdGUgc29tZSBydWxlcyB0byBjbGVhbiB0aGUgcHJpY2UNCiAgICAgICAgICAgICAgICAgICAgICAgY2FzZV93aGVuKC4kUHJpY2UudW5pdCA9PSAiTWlsbGlhcmRzIiAgJiAuJFByaWNlLnZhbHVlIDw9MTANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH4gLiRQcmljZS52YWx1ZSAqMTAwMDAwMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJfZGV0ZWN0KC4kUHJpY2UuZGVzYywgIm3CsiIpID09IFRSVUUgJiAuJFByaWNlLnZhbHVlIDw9IDM1IA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfiAuJFByaWNlLnZhbHVlICogLiRBcmVhKjEwMDAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX2RldGVjdCguJFByaWNlLmRlc2MsICJtwrIiKSA9PSBUUlVFICYgLiRQcmljZS52YWx1ZSA+IDM1ICYgLiRQcmljZS52YWx1ZSA8MTAwMDAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB+IC4kUHJpY2UudmFsdWUgKjEwMDAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX2RldGVjdCguJFByaWNlLmRlc2MsICJtwrIiKSA9PSBUUlVFICYgLiRQcmljZS52YWx1ZSA+MTAwMDAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB+IC4kUHJpY2UudmFsdWUgKiAuJEFyZWEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuJFByaWNlLnVuaXQgPT0gIk1pbGxpb25zIiYgLiRQcmljZS52YWx1ZSA8PTEwIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfiAuJFByaWNlLnZhbHVlICogMTAwMDAwMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuJFByaWNlLnVuaXQgPT0gIk1pbGxpb25zIiYgLiRQcmljZS52YWx1ZSA+MTAwIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfiAuJFByaWNlLnZhbHVlICogMTAwMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuJFByaWNlLnVuaXQgPT0gIk1pbGxpb25zIiYgLiRQcmljZS52YWx1ZSA+MTAgJiAuJFByaWNlLnZhbHVlIDwxMDAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB+LiRQcmljZS52YWx1ZSAqIDEwMDAwMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gLiRQcmljZS52YWx1ZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksDQogICAgICAgICAjIENyZWF0ZSBhIHZhcmlhYmxlIHRoYXQgY29udGFpbiBub21pbmFsIHJ1bGVzIGFwcGxpZWQgdG8gUHJpY2UgdmFyaWFibGUsIHNvIHRoYXQgd2UgY2FuIHVzZSBpdCBpbiBvdXIgc2FuaXR5IGNoZWNrDQogICAgICAgICAgICAgICBQcmljZS5ydWxlcyA9IA0KICAgICAgICAgICAgICAgICAgICAgICBjYXNlX3doZW4oLiRQcmljZS51bml0ID09ICJNaWxsaWFyZHMiICAmIC4kUHJpY2UudmFsdWUgPD0xMA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfiAiUHJpY2UudmFsdWUgKjEwMDAwMDAwIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cl9kZXRlY3QoLiRQcmljZS5kZXNjLCAibcKyIikgPT0gVFJVRSAmIC4kUHJpY2UudmFsdWUgPD0gMzUgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB+ICJQcmljZS52YWx1ZSAqIEFyZWEqMTAwMDAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX2RldGVjdCguJFByaWNlLmRlc2MsICJtwrIiKSA9PSBUUlVFICYgLiRQcmljZS52YWx1ZSA+IDM1ICYgLiRQcmljZS52YWx1ZSA8MTAwMDAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB+ICJQcmljZS52YWx1ZSAqMTAwMDAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX2RldGVjdCguJFByaWNlLmRlc2MsICJtwrIiKSA9PSBUUlVFICYgLiRQcmljZS52YWx1ZSA+MTAwMDAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB+ICJQcmljZS52YWx1ZSAqIEFyZWEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLiRQcmljZS51bml0ID09ICJNaWxsaW9ucyImIC4kUHJpY2UudmFsdWUgPD0xMCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH4gIlByaWNlLnZhbHVlICogMTAwMDAwMDAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLiRQcmljZS51bml0ID09ICJNaWxsaW9ucyImIC4kUHJpY2UudmFsdWUgPjEwMCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH4gIlByaWNlLnZhbHVlICogMTAwMDAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLiRQcmljZS51bml0ID09ICJNaWxsaW9ucyImIC4kUHJpY2UudmFsdWUgPjEwICYgLiRQcmljZS52YWx1ZSA8MTAwIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfiJQcmljZS52YWx1ZSAqIDEwMDAwMDAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJQcmljZS52YWx1ZSINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLA0KICAgICAgICAgICAgICAjIENyZWF0ZSBldmVuIG1vcmUgdmFyaWFibGVzDQogICAgICAgICAgICAgIEFubm91bmNlci5OYW1lID0gaWZlbHNlKGlzLm5hKFBzZXVkbykgPT0gRkFMU0UsUHNldWRvICxhcy5jaGFyYWN0ZXIoU3RvcmUuTmFtZSkpLA0KICAgICAgICAgICAgICBBbm5vdW5jZXIuVHlwZSA9IA0KICAgICAgICAgICAgICAgICAgICAgIGNhc2Vfd2hlbihpcy5uYShTdG9yZS5BZGRyZXNzZSkgPT0gRkFMU0UgJiANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJfZGV0ZWN0KHRvdXBwZXIoU3RvcmUuTmFtZSksICJBR0VOQ0V8QUciKSA9PSBUUlVFIH4gIkFHRU5DRSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKFN0b3JlLkFkZHJlc3NlKSA9PSBGQUxTRSAmIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cl9kZXRlY3QodG91cHBlcihTdG9yZS5OYW1lKSwgIlBST01PVElPTiIpID09IFRSVUUgfiAiUFJPTU9URVVSICIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAiQlVSRUFVIEQnQUZGQUlSRSIpLA0KICAgICAgICAgICAgICBBbm5vdW5jZXIuTmFtZSA9IGlmZWxzZShpcy5uYShQc2V1ZG8pID09IEZBTFNFLFBzZXVkbyAsYXMuY2hhcmFjdGVyKFN0b3JlLk5hbWUpKSwNCiAgICAgICAgICAgICAgSG91ciA9ICBzdHJfc3ViKHN0cl90cmltKHN0cl9zcGxpdF9maXhlZChEYXRlLCLDoCIsIDIpWywyXSksMSwyKSwNCiAgICAgICAgICAgICAgRGF0ZSA9IGRteShzdHJfdHJpbShzdHJfc3BsaXRfZml4ZWQoRGF0ZSwiw6AiLCAyKVssMV0pKSwNCiAgICAgICAgICAgICAgTW9udGggPSBtb250aChEYXRlLGxhYmVsID0gVFJVRSksDQogICAgICAgICAgICAgIE11bmljaXBhbGl0eT1mYWN0b3Ioc3RyX3RyaW0oc3RyX2V4dHJhY3QoVGl0bGUsIihBbGdlci4qKSIpKSksDQogICAgICAgICAgICAgIEdhcmFnZT1zdHJfZGV0ZWN0KFNwZWNpZmljcywgIkdhcmFnZSIpLA0KICAgICAgICAgICAgICBHYXJkZW49c3RyX2RldGVjdChTcGVjaWZpY3MsICJKYXJkaW4iKSAsDQogICAgICAgICAgICAgIEZ1cm5pc2hlZD1zdHJfZGV0ZWN0KFNwZWNpZmljcywgIk1ldWJsw6kiKSwNCiAgICAgICAgICAgICAgUHJvbWlzZT1zdHJfZGV0ZWN0KFNwZWNpZmljcywgIlByb21lc3NlIGRlIHZlbnRlIiksDQogICAgICAgICAgICAgIE5ldy5Qcm9qZWN0PXN0cl9kZXRlY3QoU3BlY2lmaWNzLCAiUHJvbW90aW9uIiksDQogICAgICAgICAgICAgIFBheW1lbnQ9ZmFjdG9yKGlmZWxzZShzdHJfZGV0ZWN0KFNwZWNpZmljcywiUGFpZW1lbnQgcGFyIHRyYW5jaGVzIikgPT0gVFJVRSwgInRyYW5jaGVzIiwgImNvbXB0YW50IikpDQogICAgICAgICAgICAgICkNCiAgICAgICAgICAgICAgIA0KIGhvdXNpbmcNCg0Kd3JpdGUuY3N2KGhvdXNpbmcsICJBbGdpZXJzX1NhbGVzQXBwYXJ0bWVudHMyNjA3MjAxOF9jLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFLCBmaWxlRW5jb2RpbmcgPSAiVVRGLTgiKSANCmBgYA0KDQo8YnI+PGJyPg0KDQojIyBIYXZpbmcgYSBsb29rIGF0IG91ciBkYXRhIGFmdGVyIGNsZWFuc2luZw0KYGBge3IgZWNobz1UUlVFLCBmaWcud2lkdGg9MTJ9DQojIyMgbGV0J3Mgc3RhcnQgYW5hbHlzaW5nIG91ciBkYXRhIGdldHdkKCkNCmhvdXNpbmc8LSByZWFkLmNzdigiQWxnaWVyc19TYWxlc0FwcGFydG1lbnRzMjYwNzIwMThfYy5jc3YiKSANCiMjIyBIYXZlIGEgbG9vayBhdCBvdXIgZXh0ZW5kZWQgSG91c2luZyAgZGF0YXNldA0KaG91c2luZyAlPiUgc2VsZWN0KERhdGUsTmIuRmxvb3IsTmIuUm9vbSxOYi5WaWV3cykgJT4lIHN1bW1hcnkoKSANCmhvdXNpbmcgJT4lIHNlbGVjdChBcmVhLFByaWNlLnZhbHVlLmR6ZCxNdW5pY2lwYWxpdHkpICU+JSBzdW1tYXJ5KCkgDQpob3VzaW5nICU+JSBzZWxlY3QoR2FyYWdlOlBheW1lbnQpICU+JSBzdW1tYXJ5KCkgDQpgYGANCg0KPGJyPjxicj4gIA0KDQojIyBsZXQncyBhcHBseSBzb21lIG1vcmUgZmlsdGVycyBhbmQgcHJlcGFyZSB0aGUgZGF0YSBmb3IgZ2VvY29kaW5nICANCg0KYGBge3IgZmlnLndpZHRoPTEyfQ0KIyMgcmVtb3ZlIGFibm9ybWFsIGRhdGEgDQpob3VzaW5nMiA8LSBob3VzaW5nICU+JQ0KICAgICAgICBmaWx0ZXIoUHJpY2UudmFsdWUuZHpkIDwxMDAwMDAwMDAgJiBQcmljZS52YWx1ZS5kemQgPj0gNDAwMDAwMCxBcmVhID4zMCAmIEFyZWEgPD0gMzAwLCAgICAjQXBwbHlpbmcgc29tZSBmaWx0ZXJzIA0KICAgICAgICAgICAgICAgc3RyX3RyaW0oTmIuUm9vbSkgJWluJSBjKCIyIiwiMyIsIjQiLCI1IikpICU+JQ0KICAgICAgICBtdXRhdGUoTmIuUm9vbSA9IGZhY3RvcihOYi5Sb29tKSkgDQpob3VzaW5nMiROYi5Sb29tIDwtIG9yZGVyZWQoc3RyX3RyaW0oaG91c2luZzIkTmIuUm9vbSkgLGxldmVscyA9MTo3KSAjT3JkZXJpbmcgdGhlIGxldmVscyBvZiB0aGUgTmIuUm9vbSB2YXJpYWJsZSAgDQoNCiMgY29uc3RydWN0IGEgZnVuY3Rpb24gZm9yIG11bHRpcGxlIHBhdHRlcm4gcmVwbGFjZW1lbnQNCm1nc3ViIDwtIGZ1bmN0aW9uKHBhdHRlcm4sIHJlcGxhY2VtZW50LCB4LCAuLi4pIHsNCiAgaWYgKGxlbmd0aChwYXR0ZXJuKSE9bGVuZ3RoKHJlcGxhY2VtZW50KSkgew0KICAgIHN0b3AoInBhdHRlcm4gYW5kIHJlcGxhY2VtZW50IGRvIG5vdCBoYXZlIHRoZSBzYW1lIGxlbmd0aC4iKQ0KICB9DQogIHJlc3VsdCA8LSB4DQogIGZvciAoaSBpbiAxOmxlbmd0aChwYXR0ZXJuKSkgew0KICAgIHJlc3VsdCA8LSBnc3ViKHBhdHRlcm5baV0sIHJlcGxhY2VtZW50W2ldLCByZXN1bHQsIC4uLikNCiAgfQ0KICByZXN1bHQNCn0gDQoNCiNyZWFkIHRoZSBzaGFwZSBmaWxlIGZvciBBbGdpZXJzDQpzaDwtcmVhZFNoYXBlUG9seSgiQzovVXNlcnMvZmF0ZWgvRG9jdW1lbnRzL1IgU2NyaXB0cy9TaGFwZWZpbGUvYWxnZXJpYS9jb21tdW5lcy5zaHAiKQ0Kc2g8LXNoW3NoQGRhdGEkd2lsYXlhPT0iQUxHRVIiLF0NCg0KDQojRG8gc29tZSBjbGVhbnNpbmcgYW5kIHByZXBhcmF0aW9uIGluIG9yZGVyIHRvIG1hdGNoIHRoZSBuYW1lcyBvZiB0aGUgbXVuaWNpcGFsaXRpZXMgaW4gb3VlZGtuaXNzIHRvIHRob3NlIA0KI2luIG91ciBzaGFwZSBmaWxlDQpob3VzaW5nMiRNdW5pY2lwYWxpdHk8LSAgdG91cHBlcihzdHJfc3BsaXRfZml4ZWQoc3RyaW5nID1ob3VzaW5nMiRNdW5pY2lwYWxpdHksIHBhdHRlcm4gPSAiICIsbiA9IDIgKVssMl0pDQpob3VzaW5nMiRNdW5pY2lwYWxpdHkgPC0gZmFjdG9yKGlmZWxzZShzdHJfZGV0ZWN0IChzdHJpbmcgPSBob3VzaW5nMiRMaW5rcyxwYXR0ZXJuID0gImFsZ2VyLWNlbnRyZSIpID09IFRSVUUgJiBpcy5uYShob3VzaW5nMiREaXN0cmljdCkgPT0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFMR0VSIENFTlRSRSIsIGFzLmNoYXJhY3Rlcihob3VzaW5nMiRNdW5pY2lwYWxpdHkpKSkgDQojQ29ycmVjdCB0aGUgbmFtaW5nDQpob3VzaW5nMiRNdW5pY2lwYWxpdHk8LSBtZ3N1YihjKCJHVUUgREUgQ09OU1RBTlRJTkUiLCAiQkFCIEVaWk9VQVIiLCJCQUNIREpFUlJBSCIsIkhBTU1BTUVUIiwgIkJJUktIQURFTSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQkVMT1VJWkRBRCIsIkJPTE9HSElORSIsIkFMR0VSIENFTlRSRSIsICJDSEVWQUxMRVkiLCJIUkFPVUEiLCJURVNTQUxBIEVMIE1FUkRKQSIsIlNBSUQgSEFNRElORSIsIkFJTiBOQUFESkEiKSwNCiAgICAgIGMoIkRKSVNSIEtTRU5USU5BIiwiQkVCIEVaWk9VQVIiLCJCQUNIIERKRVJSQUgiLCAiQkFJTlMgUk9NQUlOUyIsICJCSVIgS0hBREVNIiwgIkhBTU1BIEFOQVNTRVJTIiwNCiAgICAgICAgIkJPTE9HSElORSBJQk4gWklSSSIsICJBTEdFUiIgLCJCT1VaQVJFQUgiLCAiSEFSQU9VQSIsICJUQVNTQUxBIEVMIE1FUkRKQSIsIkJJUiBNT1VSQUQgUkFJUyIsIkRKSVNSIEtTRU5USU5BIiksDQogICAgIGhvdXNpbmcyJE11bmljaXBhbGl0eSkNCg0KaG91c2luZzIkTXVuaWNpcGFsaXR5IDwtIGZhY3RvcihpZmVsc2UoaG91c2luZzIkTXVuaWNpcGFsaXR5PT0gIiIsIkFMR0VSIiwgYXMuY2hhcmFjdGVyKGhvdXNpbmcyJE11bmljaXBhbGl0eSkpKQ0KYGBgDQoNCmBgYHtyfQ0KIyNDcmVhdGluZyB0b3AgbXVuaWNpcGFsaXRpZXMgdmFyaWFibGUNCmhvdXNpbmcyPC0gaG91c2luZzIlPiUgbXV0YXRlKFRvcF9NdW5pY2lwYWxpdGllcyA9IGZjdF9sdW1wKGhvdXNpbmcyJE11bmljaXBhbGl0eSwgMTApKSANCg0KaG91c2luZzIgJT4lIHNlbGVjdChEYXRlLE5iLkZsb29yLE5iLlJvb20sTmIuVmlld3MpICU+JSBzdW1tYXJ5KCkgDQpob3VzaW5nMiAlPiUgc2VsZWN0KEFyZWEsUHJpY2UudmFsdWUuZHpkLE11bmljaXBhbGl0eSkgJT4lIHN1bW1hcnkoKSANCmhvdXNpbmcyICU+JSBzZWxlY3QoR2FyYWdlOlBheW1lbnQpICU+JSBzdW1tYXJ5KCkgDQoNCmBgYA0KDQoNCmBgYHtyfQ0KZGF0YS5mcmFtZShUb3BfTXVuaWNpcGFsaXRpZXMgPXRhYmxlKGhvdXNpbmcyJFRvcF9NdW5pY2lwYWxpdGllcykpIyMgY2hlY2sNCg0KdW5pcXVlKHNvcnQoZmFjdG9yKGhvdXNpbmcyJE11bmljaXBhbGl0eSkpKQ0KdW5pcXVlKHNvcnQoZmFjdG9yKHNoQGRhdGEkY29tbXVuZTApKSkjIyBjaGVjayBhZ2FpbiBuZWhpIHR3ZXNzd2lzcw0KYGBgDQoNCjxicj48YnI+DQoNCiMjIEdldCBUaGUgZ2VvbG9jYXRpb24gb2YgdGhlIEhvdXNpbmcgTXVudWNpcGFsaXRpZXMgZnJvbSBHb29nbGUgTWFwIEFQSQ0KDQpgYGB7cn0NCiNHZXQgdGhlIGdlb2xvY2F0aW9uIG9mIHRoZSANCmNvbW11bmUubmFtZXMgPC0gcGFzdGUoIkFsZ2llcnMsIiwgYXMuY2hhcmFjdGVyKHVuaXF1ZShob3VzaW5nMiRNdW5pY2lwYWxpdHkpKSwgc2VwPSIgIikNCmNvbW11bmUubmFtZXNbY29tbXVuZS5uYW1lcyA9PSAiQWxnaWVycywgQUxHRVIiXSA8LSAiQUxHRVIgQ0VOVFJFLCBBbGdpZXJzIg0KY29tbXVuZS5pbmZvIDwtIGdlb2NvZGUoY29tbXVuZS5uYW1lcywgb3V0cHV0ID0gIm1vcmUiLCBvdmVycmlkZV9saW1pdCA9IFRSVUUpDQoNCiNHZXQgdGhlIGxvY2FsaXRpZXMgdGhhdCB3YXMgbm90IGdlb2NvZGVkIGluIHRoZSBkaXJzdCBydW4NCmNvbW11bmUubmFtZXMubWlzc2luZyA8LSBjYmluZChjb21tdW5lLm5hbWVzLGNvbW11bmUuaW5mbykgJT4lIA0KICAgICAgICBmaWx0ZXIoaXMubmEobG9uKSkgIA0KI1NlY29uZCBnZW9jb2RlIHBhc3MNCmNvbW11bmUuaW5mby5taXNzaW5nIDwtIGdlb2NvZGUoYXMuY2hhcmFjdGVyKGNvbW11bmUubmFtZXMubWlzc2luZyRjb21tdW5lLm5hbWVzKSwgb3V0cHV0ID0gIm1vcmUiLCBvdmVycmlkZV9saW1pdCA9IFRSVUUpDQoNCg0KI0JpbmQgcm93cyBmaXJzdCBhbmQgc2Vjb25kIGdlb2NvZGluZyBwYXNzDQpjb21tdW5lLmluZm9fZmluYWw8LQ0KICAgICAgICBjYmluZChjb21tdW5lLm5hbWVzLGNvbW11bmUuaW5mbykgJT4lDQogICAgICAgIGZpbHRlcighaXMubmEobG9uKSkgJT4lDQogICAgICAgIGJpbmRfcm93cyguLGNiaW5kKGNvbW11bmUubmFtZXM9Y29tbXVuZS5uYW1lcy5taXNzaW5nJGNvbW11bmUubmFtZXMsY29tbXVuZS5pbmZvLm1pc3NpbmcpKSANCg0KI0RvIG1vcmUgY2xlYW5zaW5nDQpjb21tdW5lLmluZm9fZmluYWwgPC0NCiAgICAgICAgbXV0YXRlKGNvbW11bmUuaW5mb19maW5hbCxjb21tdW5lLm5hbWVzID0gZmFjdG9yKGlmZWxzZShjb21tdW5lLm5hbWVzID09ICJBTEdFUiBDRU5UUkUsIEFsZ2llcnMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFsZ2llcnMsIEFMR0VSIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5jaGFyYWN0ZXIoY29tbXVuZS5uYW1lcykpKSkNCmNvbW11bmUuaW5mb19maW5hbCRNdW5pY2lwYWxpdHkgPC0gdHJpbXdzKHN0cl9zcGxpdF9maXhlZChjb21tdW5lLmluZm9fZmluYWwkY29tbXVuZS5uYW1lcywiLCIsMikpWywyXQ0KDQojQ3JlYXRlIGFuIGF1Z21lbnRlZCBkYXRhIHNldCB3aXRoIGdlb2xvY2F0aW9uIGRhdGENCkhvdXNpbmdfRGF0YSA8LSANCiAgICAgICAgaG91c2luZzIlPiUNCiAgICAgICAgbGVmdF9qb2luKC4sIGNvbW11bmUuaW5mb19maW5hbCkgJT4lDQogICAgICAgICNjcmVhdGluZyBBbm5vdW5jZXIgbmFtZSBhbmQgYW5ub3VuY2VyIFR5cGUgdmFyaWFibGUNCiAgICAgICAgbXV0YXRlKEFubm91bmNlci5OYW1lID0gaWZlbHNlKGlzLm5hKFBzZXVkbykgPT0gRkFMU0UsYXMuY2hhcmFjdGVyKFBzZXVkbykgLGFzLmNoYXJhY3RlcihTdG9yZS5OYW1lKSksDQogICAgICAgICAgICAgICBBbm5vdW5jZXIuVHlwZSA9IGNhc2Vfd2hlbihzdHJfZGV0ZWN0KHRvdXBwZXIoU3RvcmUuTmFtZSksICJBR0VOQ0V8QUciKSA9PSBUUlVFIH4gIkFHRU5DRSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJfZGV0ZWN0KHRvdXBwZXIoU3RvcmUuTmFtZSksICJQUk9NT1RJT04iKSA9PSBUUlVFIH4gIlBST01PVEVVUiIgLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX2RldGVjdCh0b3VwcGVyKFN0b3JlLk5hbWUpLCAiQlVSRUFVfEFGRkFJUkUiKSA9PSBUUlVFIH4gIkJVUkVBVSBEJ0FGRkFJUkUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEoUHNldWRvKSA9PSBGQUxTRSB+ICJQQVJUSUNVTElFUiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIkJVUkVBVSBEJ0FGRkFJUkUiKQ0KICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCmNvbW11bmUuaW5mb19maW5hbA0KSG91c2luZ19EYXRhDQpgYGANCg0KDQoNCiMjIEdldCB0aGUgZ2VvbG9jYXRpb24gb2YgdGhlIFN0b3JlKFByb21vdGV1cixBZ2VuY2UsIEJ1cmVhdSBkJ2V0dWRlcykgIA0KDQoNCmBgYHtyfQ0KI0dldHRpbmcgdGhlIE11bmljaXBhbGl0eSBvZiB0aGUgU3RvcmUgYnkgbG9va2luZyBmb3Igc2ltaWxhciBhZGRyZXNzIHBhdHRlcm4gYmV0d2VlbiB0aGUgSG91c2UgTXVuaWNpcGFsaXR5IGFuZCB0aGUgU3RvcmUgQWRkcmVzcw0KSG91c2luZ19EYXRhJFN0b3JlLk11bmljaXBhbGl0eSA8LSANCiAgICAgICAgc3RyX21hdGNoKHRvdXBwZXIoaG91c2luZzIkU3RvcmUuQWRkcmVzc2UpLA0KICAgICAgICAgICAgICAgICAgcGF0dGVybiA9IHBhc3RlKGModW5pcXVlKGFzLmNoYXJhY3Rlcihob3VzaW5nMiRNdW5pY2lwYWxpdHkpKVstMTBdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoIkdVRSBERSBDT05TVEFOVElORSIsICJCQUIgRVpaT1VBUiIsIkJBQ0hESkVSUkFIIiwiSEFNTUFNRVQiLCAiQklSS0hBREVNIiwgIkJFTE9VSVpEQUQiLCJCT0xPR0hJTkUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFMR0VSIENFTlRSRSIsICJDSEVWQUxMRVkiLCJIUkFPVUEiLCJURVNTQUxBIEVMIE1FUkRKQSIsIlNBSUQgSEFNRElORSIsIkFJTiBOQUFESkEiKSkgLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gInwiKSkgWywxXSANCiNNYXRjaGluZyB0aGUgU3RvcmUgTXVuaWNpcGFsaXR5IG5hbWVzIHdpdGggdGhlIEhvdXNpbmcgTXVuaWNpcGFsaXR5IG5hbWVzDQpIb3VzaW5nX0RhdGEkU3RvcmUuTXVuaWNpcGFsaXR5PC0gbWdzdWIoYygiR1VFIERFIENPTlNUQU5USU5FIiwgIkJBQiBFWlpPVUFSIiwiQkFDSERKRVJSQUgiLCJIQU1NQU1FVCIsICJCSVJLSEFERU0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkJFTE9VSVpEQUQiLCJCT0xPR0hJTkUiLCJBTEdFUiBDRU5UUkUiLCAiQ0hFVkFMTEVZIiwiSFJBT1VBIiwiVEVTU0FMQSBFTCBNRVJESkEiLCJTQUlEIEhBTURJTkUiLCJBSU4gTkFBREpBIiksDQogICAgICBjKCJESklTUiBLU0VOVElOQSIsIkJFQiBFWlpPVUFSIiwiQkFDSCBESkVSUkFIIiwgIkJBSU5TIFJPTUFJTlMiLCAiQklSIEtIQURFTSIsICJIQU1NQSBBTkFTU0VSUyIsDQogICAgICAgICJCT0xPR0hJTkUgSUJOIFpJUkkiLCAiQUxHRVIiICwiQk9VWkFSRUFIIiwgIkhBUkFPVUEiLCAiVEFTU0FMQSBFTCBNRVJESkEiLCJCSVIgTU9VUkFEIFJBSVMiLCJESklTUiBLU0VOVElOQSIpLA0KICAgICBIb3VzaW5nX0RhdGEkU3RvcmUuTXVuaWNpcGFsaXR5KQ0KDQojR2V0dGluZyB0aGUgbG9uZyBhbmQgbGF0IGZvciBTdG9yZSBNdW5pY2lwYWxpdHkNCkhvdXNpbmdfRGF0YSA8LWxlZnRfam9pbihIb3VzaW5nX0RhdGEsIHNlbGVjdChjb21tdW5lLmluZm9fZmluYWwsTXVuaWNpcGFsaXR5LCBTdG9yZS5sb249bG9uLCBTdG9yZS5sYXQ9bGF0KSwgYnkgPSBjKCJTdG9yZS5NdW5pY2lwYWxpdHkiID0gIk11bmljaXBhbGl0eSIpKQ0KYGBgDQoNCmBgYHtyfQ0KZ2xpbXBzZShIb3VzaW5nX0RhdGEpDQp3cml0ZS5jc3YoSG91c2luZ19EYXRhLCAiSG91c2luZ19EYXRhMjYwNzIwMTguY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpDQpgYGANCg0K